diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 3a3d24b7..00000000 --- a/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -*.swp -*.pyc -__pycache__ -*.bak -.nfs* -share/_generated/* -build* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 1929f48b..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,299 +0,0 @@ -# Prevent duplicated pipeline by removing MR pipelines -workflow: - rules: - - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - when: never - - when: always - -variables: - GIT_STRATEGY: none # don't do anything by default - SCHEDULER_PARAMETERS: "--qos=co_short_std --ntasks=1 --time=00:15:00" #Slurm default parameters - SOURCE_CMD: "/scratchm/sonics/dist/source.sh --env maia --compiler gcc@12 --mpi intel-oneapi" -#Common to all stages -default: - tags: #All jobs uses jamacar-slurm executor by default (dsi test) - - slurm - before_script: - - module purge - - source $SOURCE_CMD - -stages: - - init - - build - - test - - deploy - -job:init: - stage: init - variables: - GIT_STRATEGY: clone - GIT_SUBMODULE_STRATEGY: none - before_script: - script: # explicitly load modules one by one, so that if one fails, it will be easy to identify - - echo "CUSTOM_CI_DIR=$PWD" >> build.env - - git submodule update --init external/project_utils - - git submodule update --init external/cpp_cgns - - git submodule update --init external/std_e - - git submodule update --init external/pytest-mpi-check - - git submodule update --init external/paradigm - - (cd external/paradigm && git submodule update --init extensions/paradigma) - artifacts: - reports: - dotenv: build.env - -job:build: - stage: build - variables: - SCHEDULER_PARAMETERS: "--qos=co_short_std --ntasks=16 --nodes=1-1 --time=00:15:00" - script: - - cd $CUSTOM_CI_DIR - - mkdir -p build && cd build - - cmake -DCMAKE_BUILD_TYPE=Debug -Dmaia_ENABLE_TESTS=ON -Dmaia_ENABLE_COVERAGE=ON -DPDM_ENABLE_LONG_G_NUM=OFF -DPDM_ENABLE_EXTENSION_PDMA=ON ../ - - make -j - -# This build documentation after main build, only if branch is dev or for MR events -job:doc: - stage: build - needs: ["job:init", "job:build"] #job:init is needed to get dotenv artifacts - rules: - - if: $CI_OPEN_MERGE_REQUESTS || $CI_COMMIT_BRANCH == "dev" - script: - - cd $CUSTOM_CI_DIR/build - - source source.sh - - cmake -Dmaia_ENABLE_DOCUMENTATION=ON . - - make maia_sphinx - - -#todo : display reports on browser eg with gitlab pages -job:ctest: - stage: test - variables: - SCHEDULER_PARAMETERS: "--qos=co_short_std --ntasks=8 --nodes=1-1 --exclusive --time=00:15:00" - script: - - cd $CUSTOM_CI_DIR/build - - export PATH=$FEFLOPATH:$PATH - - ctest -R maia - after_script: - # We have to reload modules, because after_script is excuted in a separated shell - - module purge - - source $SOURCE_CMD - - cd build/test - - coverage combine --rcfile=.coveragerc_unit_tests - - coverage report --rcfile=.coveragerc_unit_tests - - coverage xml --rcfile=.coveragerc_unit_tests -o coverage_unit_tests.xml - - sed 's@'"$CI_PROJECT_DIR"'/@@' -i coverage_unit_tests.xml - when: on_success # s'exécutera uniquement si le job `job:build` passe - coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' - artifacts: - paths: - - ./build/test/reports/* - when: always - # Next allows to display a test tab in the pipeline with report but requires a feature flag to be enabled, - # see https://docs.gitlab.com/ee/ci/unit_test_reports.html#viewing-unit-test-reports-on-gitlab - # Use it in combination with --junitxml=reports/pytest-junit.xml in the pytest launcher (TestCreate.cmake) - reports: - junit: ./build/test/reports/*_test.xml - coverage_report: - coverage_format: cobertura - path: ./build/test/coverage_unit_tests.xml - -job:doc_snippets: - stage: test - script: - - cd $CUSTOM_CI_DIR/build - - source source.sh; unset PYTEST_PLUGINS - - python3 -m pytest ../doc - -job:portability_gnum: - stage: test - variables: - SCHEDULER_PARAMETERS: "--qos=co_short_std --ntasks=16 --nodes=1-1 --time=00:15:00" - rules: - - if: $CI_OPEN_MERGE_REQUESTS || $CI_COMMIT_BRANCH == "dev" - script: - - cd $CUSTOM_CI_DIR - - mkdir -p build_int64 && cd build_int64 - - cmake -DCMAKE_BUILD_TYPE=Debug -DPDM_ENABLE_LONG_G_NUM=ON -DPDM_ENABLE_EXTENSION_PDMA=ON ../ - - make -j - - source source.sh - - mpirun -np 4 python3 -m pytest ../maia/ - -job:portability_cfd5: - stage: test - variables: - SCHEDULER_PARAMETERS: "--qos=co_short_std --ntasks=16 --nodes=1-1 --time=00:15:00" - rules: - - if: $CI_OPEN_MERGE_REQUESTS || $CI_COMMIT_BRANCH == "dev" - before_script: - - module purge - - module load socle-cfd/5.0-intel2120-impi cmake/3.23.2 - script: - - cd $CUSTOM_CI_DIR - - mkdir -p build_cfd5 && cd build_cfd5 - - cmake ../ -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DCMAKE_CXX_STANDARD=17 - -DCMAKE_EXE_LINKER_FLAGS='-lz -lbz2' -DCMAKE_SHARED_LINKER_FLAGS='-lz -lbz2' - -DPDM_ENABLE_LONG_G_NUM=OFF -DPDM_ENABLE_EXTENSION_PDMA=ON - - make -j - - source $ELSASPIRO/.env_elsA - - source source.sh - - mpirun -np 4 python3 -m pytest ../maia/ - - -# Deploy the documentation sphinx documentation to the site and pages, only if branch is dev -pages: - stage: deploy - rules: - - if: $CI_COMMIT_BRANCH == "dev" - before_script: - - '' - script: - - mkdir public - - cp -r /scratchm/sonics/usr/maia/v1.0/doc/html public/1.0 - - cp -r /scratchm/sonics/usr/maia/v1.1/doc/html public/1.1 - - cp -r /scratchm/sonics/usr/maia/v1.2/doc/html public/1.2 - - cp -r /scratchm/sonics/usr/maia/v1.3/doc/html public/1.3 - - cp -r $CUSTOM_CI_DIR/build/doc/sphinx/html public/dev - - | - cat > public/index.html << EOF - - - - EOF - - artifacts: - paths: - - public - -job:init_deploy: - stage: deploy - rules: - - if: $CI_COMMIT_BRANCH == "dev" - tags: - - shell - variables: - GIT_STRATEGY: clone - before_script: - - git submodule update --init - - (cd external/paradigm && git submodule update --init) - script: - - rsync -a . sonics@spiro02:/scratchm/sonics/tmp/maia-ci-${CI_PIPELINE_ID} - - rsync -a . sonics@sator:/tmp_user/sator/sonics/tmp/maia-ci-${CI_PIPELINE_ID} - - rsync -a --delete . sonics@spiro02:/stck/sonics/tmp/maia-ci-dev #This one is for LD8 - -job:deploy_spiro_socle5: - stage: deploy - needs: ["job:init_deploy"] - rules: - - if: $CI_COMMIT_BRANCH == "dev" - tags: - - shell - before_script: - - | - cat > build_spiro5.sh << EOF - module purge - module load socle-cfd/5.0-intel2120-impi cmake/3.23.2 - export https_proxy=http://proxy.onera:80 - - cmake ../ -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_INSTALL_PREFIX=/scratchm/sonics/usr/maia/dev/dsi-cfd5_dbg \ - -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_EXE_LINKER_FLAGS='-lz -lbz2' -DCMAKE_SHARED_LINKER_FLAGS='-lz -lbz2' \ - -DPDM_ENABLE_LONG_G_NUM=OFF -DPDM_ENABLE_EXTENSION_PDMA=ON - make -j install > dsi-cfd5_dbg.log - - cmake . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/scratchm/sonics/usr/maia/dev/dsi-cfd5 - make -j install > dsi-cfd5.log - EOF - script: - - export JOB_BUILD_DIR=/scratchm/sonics/tmp/maia-ci-${CI_PIPELINE_ID}/build-${CI_JOB_ID}/ - - echo "Build directory is ${JOB_BUILD_DIR}" - - rsync build_spiro5.sh sonics@spiro02:${JOB_BUILD_DIR} - - ssh sonics@spiro01 "cd $JOB_BUILD_DIR; source /etc/profile; sh ./build_spiro5.sh" - -job:deploy_spiro_sonics: - stage: deploy - needs: ["job:init_deploy"] - rules: - - if: $CI_COMMIT_BRANCH == "dev" - tags: - - shell - before_script: - - | - cat > build_spiro.sh << EOF - module purge - source /scratchm/sonics/dist/source.sh --env maia --compiler gcc@12 --mpi intel-oneapi - export https_proxy=http://proxy.onera:80 - export PREFIX=/scratchm/sonics/usr/maia/dev - FLAGS="-DPDM_ENABLE_LONG_G_NUM=OFF -DPDM_ENABLE_EXTENSION_PDMA=ON" - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=\$PREFIX/default_dbg \$FLAGS ../ - make -j install > default_dbg.log - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\$PREFIX/default . - make -j install > default.log - EOF - script: - - export JOB_BUILD_DIR=/scratchm/sonics/tmp/maia-ci-${CI_PIPELINE_ID}/build-${CI_JOB_ID}/ - - echo "Build directory is ${JOB_BUILD_DIR}" - - rsync build_spiro.sh sonics@spiro02:${JOB_BUILD_DIR} - - ssh sonics@spiro02 "cd $JOB_BUILD_DIR; source /etc/profile; sh ./build_spiro.sh" - -job:deploy_sator_socle5: - stage: deploy - needs: ["job:init_deploy"] - rules: - - if: $CI_COMMIT_BRANCH == "dev" - tags: - - shell - before_script: - - | - cat > build_sator5.sh << EOF - module purge - module load socle-cfd/5.0-intel2120-impi cmake/3.23.2 - export https_proxy=http://proxy.onera:80 - cmake ../ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/tmp_user/sator/sonics/usr/maia/dev/dsi-cfd5 \ - -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_EXE_LINKER_FLAGS='-lz -lbz2' -DCMAKE_SHARED_LINKER_FLAGS='-lz -lbz2' \ - -DPDM_ENABLE_LONG_G_NUM=ON -DPDM_ENABLE_EXTENSION_PDMA=ON - make -j install > dsi-cfd5.log - EOF - script: - - export JOB_BUILD_DIR=/tmp_user/sator/sonics/tmp/maia-ci-${CI_PIPELINE_ID}/build-${CI_JOB_ID}/ - - echo "Build directory is ${JOB_BUILD_DIR}" - - rsync build_sator5.sh sonics@sator:${JOB_BUILD_DIR} - - ssh sonics@sator "cd $JOB_BUILD_DIR; source /etc/profile; sh ./build_sator5.sh" - -job:deploy_sator_sonics: - stage: deploy - needs: ["job:init_deploy"] - rules: - - if: $CI_COMMIT_BRANCH == "dev" - tags: - - shell - before_script: - - | - cat > build_sator.sh << EOF - source /tmp_user/sator/sonics/dist/source.sh --env maia --compiler gcc@12 --mpi intel-oneapi - export https_proxy=http://proxy.onera:80 - cmake ../ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/tmp_user/sator/sonics/usr/maia/dev/default \ - -DPDM_ENABLE_LONG_G_NUM=ON -DPDM_ENABLE_EXTENSION_PDMA=ON - make -j install > default.log - EOF - script: - - export JOB_BUILD_DIR=/tmp_user/sator/sonics/tmp/maia-ci-${CI_PIPELINE_ID}/build-${CI_JOB_ID}/ - - echo "Build directory is ${JOB_BUILD_DIR}" - - rsync build_sator.sh sonics@sator:${JOB_BUILD_DIR} - - ssh sonics@sator "cd $JOB_BUILD_DIR; source /etc/profile; sh ./build_sator.sh" - -job:post_deploy: - stage: deploy - needs: ["job:deploy_spiro_socle5", "job:deploy_spiro_sonics", "job:deploy_sator_socle5", "job:deploy_sator_sonics"] - rules: - - if: $CI_COMMIT_BRANCH == "dev" - tags: - - shell - before_script: - script: - - ssh sonics@spiro02 "rm -rf /scratchm/sonics/tmp/maia-ci-${CI_PIPELINE_ID}" - - ssh sonics@sator "rm -rf /tmp_user/sator/sonics/tmp/maia-ci-${CI_PIPELINE_ID}" diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 128e9170..00000000 --- a/.gitmodules +++ /dev/null @@ -1,15 +0,0 @@ -[submodule "external/std_e"] - path = external/std_e - url = https://github.com/onera/std_e.git -[submodule "external/cpp_cgns"] - path = external/cpp_cgns - url = https://github.com/onera/cpp_cgns.git -[submodule "external/project_utils"] - path = external/project_utils - url = https://github.com/onera/project_utils.git -[submodule "external/pytest-mpi-check"] - path = external/pytest-mpi-check - url = https://github.com/onera/pytest_parallel.git -[submodule "external/paradigm"] - path = external/paradigm - url = git@gitlab.onera.net:numerics/mesh/paradigm.git diff --git a/AUTHORS.md b/AUTHORS.md deleted file mode 100644 index ed17d45f..00000000 --- a/AUTHORS.md +++ /dev/null @@ -1,16 +0,0 @@ -Main authors ------------- - -Bérenger Berthoul , ONERA -Julien Coulet , ONERA -Bruno Maugars , ONERA - - -Contributors ------------ -Clément Benazet , ONERA -Sébastien Bourasseau , ONERA -Bertrand Michel , ONERA -Mickael Philit , SAFRAN -Constance Timmermans, ONERA -Hedi El Amami, ONERA diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 15720bd6..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,227 +0,0 @@ -# ------------------------------------------------------------------------------ -# General CMake settings -# ------------------------------------------------------------------------------ -cmake_minimum_required(VERSION 3.14 FATAL_ERROR) -cmake_policy(SET CMP0074 NEW) # force find_package to take _ROOT variables into account - -if(NOT DEFINED PROJECT_ROOT) - set(PROJECT_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root directory, where the submodules are populated") -endif() - -# Bootstrap project_utils -set(sub_repo_path "${PROJECT_ROOT}/external/project_utils") -file(GLOB sub_repo_files ${sub_repo_path}/*) -list(LENGTH sub_repo_files sub_repo_nb_files) -if(sub_repo_nb_files EQUAL 0) - message(FATAL_ERROR - "${sub_repo_path} is empty. - Maybe you forgot to initialize it with \"git submodule update --init\"" - ) -endif() -set(PROJECT_UTILS_DIR ${PROJECT_ROOT}/external/project_utils) -set(PROJECT_UTILS_CMAKE_DIR ${PROJECT_UTILS_DIR}/cmake) -list(APPEND CMAKE_MODULE_PATH "${PROJECT_UTILS_CMAKE_DIR}/find_package") # for custom Find*.cmake files -include(${PROJECT_UTILS_CMAKE_DIR}/dependency_management.cmake) # project_add_subdirectory, project_find_package and target_install - -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") - - -# ------------------------------------------------------------------------------ -# Project -# ------------------------------------------------------------------------------ -project( - maia VERSION 0.1.0 - DESCRIPTION "Distributed algorithms for CGNS trees" - LANGUAGES CXX -) - -option(${PROJECT_NAME}_ENABLE_COVERAGE "Enable coverage for ${PROJECT_NAME}" OFF) -option(${PROJECT_NAME}_ENABLE_DOCUMENTATION "Build ${PROJECT_NAME} documentation" OFF) -option(${PROJECT_NAME}_BUILD_EMBEDDED_PDM "Build the included copy of ParaDiGM" ON) -option(${PROJECT_NAME}_ENABLE_TESTS "Make CTest run the tests" ON) - -## Compiler flags -### C++ standard -set(SUPPORTED_CXX_STANDARDS 17 20) -if(NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20 CACHE STRING "C++ standard to compile against") -endif() -if(NOT CMAKE_CXX_STANDARD IN_LIST SUPPORTED_CXX_STANDARDS) - message(FATAL_ERROR "Supported CXX standards are: ${SUPPORTED_CXX_STANDARDS}.") -endif() - -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD_REQUIRED ON ) -### fPIC -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -### Compiler-dependent flags -include(${PROJECT_UTILS_CMAKE_DIR}/default_flags.cmake) -### Default build type -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) # default to Release -endif() -### Additionnal build types -include(${PROJECT_UTILS_CMAKE_DIR}/additional_build_types.cmake) - - -# ------------------------------------------------------------------------------ -# Dependencies -# ------------------------------------------------------------------------------ -## System dependencies ### -if (NOT TARGET Python::Python OR NOT TARGET Python::NumPy) - project_find_package(Python REQUIRED COMPONENTS Development NumPy) -endif() -if (NOT TARGET MPI::MPI_CXX) - project_find_package(MPI REQUIRED COMPONENTS CXX) -endif() -if (NOT TARGET Mpi4Py::Mpi4Py) - project_find_package(Mpi4Py REQUIRED) -endif() - -## Dependencies built from source ## -include(${PROJECT_UTILS_CMAKE_DIR}/find_or_fetch.cmake) -find_or_fetch_pybind11() - -### ParaDiGM ### -if (NOT ${PROJECT_NAME}_BUILD_EMBEDDED_PDM) - project_find_package(pdm CONFIG REQUIRED) - project_find_package(pdma CONFIG) -else() - # Maia uses ParaDiGM with these options - set(PDM_ENABLE_SHARED ON CACHE BOOL "Maia uses ParaDiGM with shared libs" FORCE) - set(PDM_ENABLE_PYTHON_BINDINGS ON CACHE BOOL "Maia uses ParaDiGM with python" FORCE) - set(PDM_ENABLE_MPI_CHECK OFF CACHE BOOL "Maia uses ParaDiGM without MPI check" FORCE) - set(PDM_ENABLE_UNIT_TEST OFF CACHE BOOL "Maia uses ParaDiGM without unit tests" FORCE) - set(PASS_DEFAULT_FLAGS ON CACHE BOOL "Maia does not use ParaDiGM default flags" FORCE) - # These option values are not FORCE, but the default is different than the one of ParaDiGM - set(PDM_ENABLE_Fortran OFF CACHE BOOL "Maia uses ParaDiGM without Fortran" ) - set(PDM_ENABLE_STATIC OFF CACHE BOOL "Maia uses ParaDiGM without static libs") - # Adding paradigm sources from external/paradigm - include(${PROJECT_UTILS_CMAKE_DIR}/check_local_dependency.cmake) - check_local_dependency(paradigm REQUIRED) - project_add_subdirectory(paradigm) -endif() - -### std_e + cpp_cgns ### -set(std_e_ENABLE_MPI ON CACHE BOOL "Maia uses std_e with mpi" FORCE) -project_add_subdir_or_package(std_e REQUIRED) -project_add_subdir_or_package(cpp_cgns REQUIRED) - -## Check Python dependencies ## -include(${PROJECT_UTILS_CMAKE_DIR}/check_python_module.cmake) -check_python_module(ruamel REQUIRED) - - -# ------------------------------------------------------------------------------ -# Code quality tools TODO -# ------------------------------------------------------------------------------ -find_program( MEMORYCHECK_COMMAND valgrind ) -set( MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full" ) - -# > Mandatory to put here to have all shared libs compile with the coverage flags -#if(${${PROJECT_NAME}_ENABLE_COVERAGE}) - #include(CodeCoverage) - #append_coverage_compiler_flags() -#endif() - - -# ------------------------------------------------------------------------------ -# Compilation -# ------------------------------------------------------------------------------ -set(src_dir ${PROJECT_SOURCE_DIR}/${PROJECT_NAME}) -set(test_dir ${PROJECT_SOURCE_DIR}/test) -set(include_dir ${PROJECT_SOURCE_DIR}) - -file(GLOB_RECURSE all_src_files - CONFIGURE_DEPENDS "${src_dir}/*.cpp" -) - -set(src_files ${all_src_files}) -list(FILTER src_files EXCLUDE REGEX ".*\\.test\\.cpp$") -list(FILTER src_files EXCLUDE REGEX ".*\\.pybind\\.cpp$") - -add_library(${PROJECT_NAME} SHARED ${src_files}) - -target_include_directories(${PROJECT_NAME} PUBLIC - $ - $ -) - -target_link_libraries(${PROJECT_NAME} - PUBLIC - MPI::MPI_CXX - std_e::std_e - cpp_cgns::cpp_cgns - pdm::pdm_shared - Mpi4Py::Mpi4Py -) - -# ------------------------------------------------------------------------------ -# Create file to source build environnement -# ------------------------------------------------------------------------------ -include(${PROJECT_UTILS_CMAKE_DIR}/write_build_env_file.cmake) -write_build_env_file() - -# ---------------------------------------------------------------------- -# Copy logging.conf example to build folder -# ---------------------------------------------------------------------- -if (EXISTS "${PROJECT_BINARY_DIR}/logging.conf") - message("File ${PROJECT_BINARY_DIR}/logging.conf already present") -else() - configure_file( - ${PROJECT_SOURCE_DIR}/share/logging.conf.example - ${PROJECT_BINARY_DIR}/logging.conf - @ONLY - ) -endif() - -# ------------------------------------------------------------------------------ -# Installation -# ------------------------------------------------------------------------------ -target_install(${PROJECT_NAME}) - -# ------------------------------------------------------------------------------ -# Python and wrapping -# ------------------------------------------------------------------------------ -include(${PROJECT_UTILS_CMAKE_DIR}/python_wrapping.cmake) -compile_install_pybind_module(${PROJECT_NAME}) -install_python_modules(${PROJECT_NAME}) - - -# ------------------------------------------------------------------------------ -# Testing -# ------------------------------------------------------------------------------ -include(CTest) -if (${PROJECT_NAME}_ENABLE_TESTS) - add_subdirectory(test) -endif() - - -# ------------------------------------------------------------------------------ -# Build documentation -# ------------------------------------------------------------------------------ -if(${PROJECT_NAME}_ENABLE_DOCUMENTATION) - include(${PROJECT_UTILS_CMAKE_DIR}/build_documentation.cmake) - build_sphinx_documentation() -endif() - - -# ------------------------------------------------------------------------------ -# Install scripts -# ------------------------------------------------------------------------------ -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/ - DESTINATION ${CMAKE_INSTALL_PREFIX}/bin - FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE) - - -# ------------------------------------------------------------------------------ -# Summary -# ------------------------------------------------------------------------------ -get_directory_property(top_lvl_cmakelists PARENT_DIRECTORY) -if(top_lvl_cmakelists STREQUAL ${PROJECT_SOURCE_DIR}) - include(FeatureSummary) - feature_summary(WHAT ALL) -endif() diff --git a/README.md b/README.md index 15429bc4..d285481d 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,17 @@ -**Maia** is a Python and C++ library for parallel algorithms and manipulations over CGNS meshes. Maia introduces a parallel representation of the CGNS trees and uses [ParaDiGM](https://gitlab.onera.net/numerics/mesh/paradigm/) as a back-end to provide various functions applicable to these trees. - -## Getting started ## - -### Onera users -User documentation is deployed on the Gitlab pages server: https://numerics.gitlab-pages.onera.net/mesh/maia/index.html. - -Stable installations are provided on Spiro and Sator clusters: for example, on Spiro-EL8 partition, Maia environment can be loaded with the following lines: - -```bash -source /scratchm/sonics/dist/source.sh --env maia -module load maia/dev-default -``` - -Additional environments are provided in the [Quick start](https://numerics.gitlab-pages.onera.net/mesh/maia/quick_start.html) page of the documention. - -## Other users - -See the next section to build your own version of Maia. - -## Build and install ## - -Follow these steps to build Maia from the sources: - -1. `git clone git@gitlab.onera.net:numerics/mesh/maia.git` -2. `cd maia` -3. `git submodule update --init` (needed for dependencies) -4. `(cd external/paradigm && git submodule update --init extensions/paradigma)` (enable advanced features) -5. Use `cmake` to configure, build and install. See the complete procedure here `doc/installation.rst` - -Documentation can be build with `cmake` flag `-Dmaia_ENABLE_DOCUMENTATION=ON` - -## Contributing ## -`Maia` is open-source software. Contributions are welcome. See `Contributing`. - -Issues can be reported directly on [the Issues section](https://gitlab.onera.net/numerics/mesh/maia/-/issues). - -## License ## -`Maia` is available under the MPL-2.0 license (https://mozilla.org/MPL/2.0/). - -

- logo-maia -

\ No newline at end of file +This branch is only here to publish documentation on github.io server. + +Follow these steps to do it: + +- Copy the generated html directory of each version in `docs/` folder +- Create a file named `.nojekyll` in each version folder. Otherwise, javascript rendering will be + desactivated +- Create of update the `docs/index.html` file to make it redirect to lastest doc version, for exemple : + ```html + + + + ``` +- The link to others version is hardcoded in each html file. For github, it must be replaced by `/Maia/v_id`. To do that, execute this line in `docs/` directory: + `find . -name '*.html' -exec sed -i 's@href="/mesh/maia/@href="/Maia/@' {} +` +- To delete the reference to dev version, use in the same way + `find . -name '*.html' -exec sed -i '/
/d' {} +` diff --git a/doc/_drafts/connectivity_vocabulary.rst.draft b/doc/_drafts/connectivity_vocabulary.rst.draft deleted file mode 100644 index 9b515eec..00000000 --- a/doc/_drafts/connectivity_vocabulary.rst.draft +++ /dev/null @@ -1,33 +0,0 @@ -Generating element connectivities -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -TODO: - -vocabulary cell_txt.... - -* cell_vtx -> face_vtx, face_cell ("fetch") -* face_vtx, face_cell -> cell_vtx -* cell_cell, vtx_cell, vtx_vtx, edge_vtx - -Grid connectivities -^^^^^^^^^^^^^^^^^^^ - -* face -> vtx -* vtx -> face - -Renumbering -^^^^^^^^^^^ - -* partitions alone -* partitions + update LN_to_GN - - -Extended partitions -^^^^^^^^^^^^^^^^^^^ - -* Ghost cells, ghost nodes -* Reveral ranks - - - - diff --git a/doc/_drafts/disttree_s_to_u.rst.draft b/doc/_drafts/disttree_s_to_u.rst.draft deleted file mode 100644 index 8a6d75e5..00000000 --- a/doc/_drafts/disttree_s_to_u.rst.draft +++ /dev/null @@ -1,58 +0,0 @@ -Conversion Structuré - Non structuré distribuée -=============================================== - -Cette note reprend le descriptif du developpement `merge_request_3 -`_ -en attendant une meilleure intégration dans la documentation. - -Dev permettant de convertir un arbre distribué structuré en arbre distribué non structuré. - -Étant donnée que le nouveau chargement du dist_tree S produit des tableaux "à plat" (grâce aux slabs), -il n'y a rien à transformer pour les données (vertex, flowsolution, bcdata, etc.). -Il faut en revanche générer, de manière consistante avec les slabs de données, - -- la connectivité NGON; -- les pointLists à partir des pointRanges. - -Les deux points s'appuient sur la création d'une numérotation non structuré du maillage cartésien en i,j,k croissant, -et on retrouve dans le code deux briques correspondant à ces deux fonctionnalités : `compute_all_ngon_connectivity` -et `compute_pointList_from_pointRanges`. La première est utilisée pour les NGon tandis que la seconde est utilisée -pour les BC et GC (dans ce dernier cas elle permet d'obtenir le PL et PLDonor, l'idée étant que l'un des -deux joins impose son découpage en slab à l'autre pour conserver le match). - -Quelques notes: - -- La construction des ngons pourrait être isolée de manière à générer directement un disttree U à - partir de la taille de la zone, à la manière de cartNGon(). -- Les zones subregion ne sont pas encore traitées, mais à priori : - - + s'il n'y a que de la donnée (et pas de pointRange spécifique, *ie* la SZR est liée à une BC/GC) : - il faut juste copier la data - + s'il y a un PR spécifique, on devrait pouvoir appeler `compute_pointList_from_pointRanges`. - Néanmoins cette function n'a pas été testée dans le cas d'un PointRange représentant un volume. - -- Même chose pour les BCDataSet, mais cette fois sans la réserve sur le PR volumique. -- L'utilisateur à la possibilité de choisir la gridLocation attendue en sortie pour les BC/GC. - Mais je m’aperçois à l'instant qu'il faudrait empêcher cela si un BCDataSet est présent, puisque - on ne serait dans ce cas plus consistant avec la donnée. -- Effets de bords: - - + modification de `point_range_size` qui permet de calculer la taille d'un PR pour gérer les PR inversé : - en effet il est permis d'avoir un PR du style [[1,3], [5,5], [**4,1**]], ce qui arrive sur les joins. - + correction dans `cgns_io/correct_tree.py` : les GridConnectivityProperty n'étaient pas chargés sur les - raccords *1to1*. - + ajout d'un correction sur les PR dans `cgns_io/correct_tree.py` : on s'est rendu compte que cassiopée - refuse le cas précédent d'un PR inversé, et les remet donc dans l'ordre croissant, ce qui casse le lien - avec le *transform*. On remet donc tout d'équerre lors de la lecture de l'arbre si un incohérence avec le - *transform* est détectée. - -Reste à faire : - -- il faudrait ajouter, en plus des TU, un test "fonctionnel" qui illustrerait l'usage de la fonction top-level. - A priori on attend d'avoir plus de recul sur l'organisation de ces tests pour mettre ça en place. -- BCDS et ZoneSubRegion (urgent ?) - -La perfo à l'air assez bonne, j'ai mesuré sur un cube S 193x193x193 avec 6 BCs : 18 secondes en sequentiel -et environ 4 secondes sur 4 coeurs pour la conversion ! - - diff --git a/doc/_drafts/loading_a_distributed_tree.rst.draft b/doc/_drafts/loading_a_distributed_tree.rst.draft deleted file mode 100644 index f7ae0d3b..00000000 --- a/doc/_drafts/loading_a_distributed_tree.rst.draft +++ /dev/null @@ -1,28 +0,0 @@ -.. contents:: :local: - -.. _load_dist_tree: - -Loading a distributed tree -========================== - -A *dist tree* is loaded in 3 steps : -1. Collectively load a *size tree* -2. Deduce a distribution over the processes from the sizes -3. Create a **hdf selector** -4. Load the *dist tree* in parallel - - -Filters -------- - -fichier contient dataspace -noeud CGNS <-> dataspace hdf -citer doc hdf -infos: -https://support.hdfgroup.org/HDF5/Tutor/selectsimple.html -https://support.hdfgroup.org/HDF5/doc1.6/UG/12_Dataspaces.html - début, taille de block, taille pour chaque block, stride -DSMMRYDA offset, stride, size, block (hdf) -DSFILEDA offset, stride, size, block (hdf) -DSGLOBDA taille totale -DSFORMDA 0: contigu, 1: interlacé, 2: concaténé interlacé diff --git a/doc/_templates/versions.html b/doc/_templates/versions.html deleted file mode 100644 index 2cc45e2f..00000000 --- a/doc/_templates/versions.html +++ /dev/null @@ -1,51 +0,0 @@ -{% if READTHEDOCS or display_lower_left %} -{# Add rst-badge after rst-versions for small badge style. #} -
- - Read the Docs - v: {{ current_version }} - - -
- {% if languages|length >= 1 %} -
-
{{ _('Languages') }}
- {% for slug, url in languages %} -
{{ slug }}
- {% endfor %} -
- {% endif %} - {% if versions|length >= 1 %} -
-
{{ _('Versions') }}
- {% for slug, url in versions %} -
{{ slug }}
- {% endfor %} -
- {% endif %} - {% if downloads|length >= 1 %} -
-
{{ _('Downloads') }}
- {% for type, url in downloads %} -
{{ type }}
- {% endfor %} -
- {% endif %} - {% if READTHEDOCS %} -
-
{{ _('On Read the Docs') }}
-
- {{ _('Project Home') }} -
-
- {{ _('Builds') }} -
-
- {% endif %} -
- {% trans %}Free document hosting provided by Read the Docs.{% endtrans %} - -
-
-{% endif %} - diff --git a/doc/conf.py b/doc/conf.py deleted file mode 100644 index 0618fa04..00000000 --- a/doc/conf.py +++ /dev/null @@ -1,134 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- Project information ----------------------------------------------------- - -project = 'Maia' -copyright = '2021, ONERA The French Aerospace Lab' -author = 'ONERA' - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ["sphinx.ext.graphviz", - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinx.ext.napoleon"] - -add_module_names = False #Shorten function names -autodoc_typehints = 'none' #Hide typehints in doc - -# -- Napoleon extension settings -napoleon_use_rtype = False # Don't add a line for return type - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# These paths are either relative to html_static_path -# or fully qualified paths (eg. https://...) -#html_css_files = [ -# 'custom.css', -#] - -html_style = 'css/read_the_docs_custom.css' - -# The name of the Pygments (syntax highlighting) style to use. -# list with >>> from pygments.styles import STYLE_MAP; STYLE_MAP.keys() -#pygments_style = 'monokai' - - -html_theme_options = { - 'navigation_depth': 6, -} - -# Graphviz config -graphviz_output_format = "svg" - -rst_prolog = """ -.. role:: cpp(code) - :language: c++ - -.. role:: cgns - -.. role:: mono - -.. role:: def - -.. role:: titlelike -""" - -# Generate cgns example files, some will be downloadable -import subprocess -subprocess.run(["../scripts/maia_yaml_examples_to_hdf5", "../share/_generated"], stdout=subprocess.DEVNULL) - -############################ -# SETUP THE RTD LOWER-LEFT # -############################ -# See https://github.com/maltfield/rtd-github-pages for details -try: - html_context -except NameError: - html_context = dict() -html_context['display_lower_left'] = True - -REPO_NAME = 'mesh/maia' #Namespace in the gitlab pages server -current_language = 'en' -current_version = 'dev' - -# tell the theme which language to we're currently building -html_context['current_language'] = current_language -# tell the theme which version we're currently on ('current_version' affects -# the lower-left rtd menu and 'version' affects the logo-area version) -html_context['current_version'] = current_version -if current_version == 'dev': - html_context['version'] = current_version -else: - html_context['version'] = f'v{current_version}' - -# POPULATE LINKS TO OTHER LANGUAGES -html_context['languages'] = list() -for lang in []: - html_context['languages'].append( (lang, f'/{REPO_NAME}/{lang}/{current_version}/') ) - -# POPULATE LINKS TO OTHER VERSIONS -html_context['versions'] = list() -for version in ['dev', '1.3', '1.2', '1.1', '1.0']: - html_context['versions'].append( (version, f'/{REPO_NAME}/{version}/') ) - -# POPULATE LINKS TO OTHER FORMATS/DOWNLOADS -html_context['downloads'] = list() diff --git a/doc/developer_manual/snippets/test_logging.py b/doc/developer_manual/snippets/test_logging.py deleted file mode 100644 index 8d58254d..00000000 --- a/doc/developer_manual/snippets/test_logging.py +++ /dev/null @@ -1,29 +0,0 @@ -def test_logger(): - #add_logger@start - from maia.utils.logging import add_logger - - add_logger("my_logger") - #add_logger@end - #log@start - from maia.utils.logging import log - - log('my_logger', 'my message') - #log@end - - -def test_printer(): - #add_printer@start - from maia.utils.logging import add_printer_to_logger - add_printer_to_logger('my_logger','stdout_printer') - add_printer_to_logger('my_logger','file_printer("my_file.log")') - #add_printer@end - - #create_printer@start - from maia.utils.logging import add_printer_to_logger - - class my_printer: - def log(self, msg): - print(msg) - - add_printer_to_logger('my_logger',my_printer()) - #create_printer@end diff --git a/doc/images/qs_post_basic.py b/doc/images/qs_post_basic.py deleted file mode 100644 index c4842a0e..00000000 --- a/doc/images/qs_post_basic.py +++ /dev/null @@ -1,17 +0,0 @@ -import numpy as np -import maia -import maia.pytree as PT - -tree = maia.io.read_tree('U_twoblocks.cgns') - -i_bc = 0 -for zone in PT.get_all_Zone_t(tree): - face_flag = -1*np.ones(PT.Zone.n_face(zone), dtype=int) - for bc in PT.get_nodes_from_label(zone, 'BC_t'): - pl = PT.get_node_from_name(bc, 'PointList')[1][0] - face_flag[pl-1] = i_bc - i_bc += 1 - PT.new_FlowSolution('FS', loc='FaceCenter', fields={'FaceFlag': face_flag}, parent=zone) - -maia.io.write_tree(tree, 'U_twoblocks2.cgns') - diff --git a/doc/images/qs_pycgns.pvsm b/doc/images/qs_pycgns.pvsm deleted file mode 100755 index 948900c7..00000000 --- a/doc/images/qs_pycgns.pvsm +++ /dev/null @@ -1,15931 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/images/qs_workflow.pvsm b/doc/images/qs_workflow.pvsm deleted file mode 100644 index 1c2231c5..00000000 --- a/doc/images/qs_workflow.pvsm +++ /dev/null @@ -1,12348 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/introduction/images/dist_part/dist_part.pptx b/doc/introduction/images/dist_part/dist_part.pptx deleted file mode 100755 index 4d718606..00000000 Binary files a/doc/introduction/images/dist_part/dist_part.pptx and /dev/null differ diff --git a/doc/introduction/images/dist_part/dist_part_LN_to_GN.pptx b/doc/introduction/images/dist_part/dist_part_LN_to_GN.pptx deleted file mode 100755 index 5bd380b6..00000000 Binary files a/doc/introduction/images/dist_part/dist_part_LN_to_GN.pptx and /dev/null differ diff --git a/doc/introduction/images/trees/4_cubes_lite.yaml b/doc/introduction/images/trees/4_cubes_lite.yaml deleted file mode 100644 index 389f2cc6..00000000 --- a/doc/introduction/images/trees/4_cubes_lite.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Incomplete tree, used for uncluttered examples in the documentation -MyBase [3,3]: - Wall Family_t: - BCWall FamilyBC_t: - MyZone [18 4 0]: - GridCoordinates: - CoordinateX [0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2]: - Hexa [17 0]: - ElementRange [1 4]: - ElementConnectivity [1 2 5 4 10 11 14 13 2 3 6 5 11 12 15 14 4 5 8 7 13 14 17 16 5 6 9 8 14 15 18 17]: - Quad [7 0]: - ElementRange [5 6]: - FlowSolution: - Density [1.0 1.2 1.3 1.2]: - ZoneBC: - BC_1: - FamilyName "Wall": - PointList [5 6]: - GridLocation "FaceCenter": - BCDataSet: - DirichletData: - Temperature [1. 2.]: diff --git a/doc/introduction/images/trees/4_cubes_lite_dist_0.yaml b/doc/introduction/images/trees/4_cubes_lite_dist_0.yaml deleted file mode 100644 index b2c3c46e..00000000 --- a/doc/introduction/images/trees/4_cubes_lite_dist_0.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Incomplete tree, used for uncluttered examples in the documentation -MyBase [3,3]: - Wall Family_t: - BCWall FamilyBC_t: - MyZone [18 4 0]: - :CGNS#Distribution: - Vertex [0 9 18]: - Cell [0 3 4]: - GridCoordinates: - CoordinateX [0 1 2 0 1 2 0 1 2]: - Hexa [17 0]: - ElementRange [1 4]: - ElementConnectivity [1 2 5 4 10 11 14 13 2 3 6 5 11 12 15 14]: - :CGNS#Distribution: - Element [0 2 4]: - Quad [7 0]: - ElementRange [5 6]: - FlowSolution: - Density [1.0 1.2 1.3]: - ZoneBC: - BC_1: - FamilyName "Wall": - PointList [5]: - :CGNS#Distribution: - Index [0 1 2]: - GridLocation "FaceCenter": - BCDataSet: - DirichletData: - Temperature [1.]: diff --git a/doc/introduction/images/trees/4_cubes_lite_dist_1.yaml b/doc/introduction/images/trees/4_cubes_lite_dist_1.yaml deleted file mode 100644 index 8ef02aa3..00000000 --- a/doc/introduction/images/trees/4_cubes_lite_dist_1.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Incomplete tree, used for uncluttered examples in the documentation -MyBase [3,3]: - Wall Family_t: - BCWall FamilyBC_t: - MyZone [18 4 0]: - :CGNS#Distribution: - Vertex [9 18 18]: - Cell [3 4 4]: - GridCoordinates: - CoordinateX [0 1 2 0 1 2 0 1 2]: - Hexa [17 0]: - ElementRange [1 4]: - ElementConnectivity [4 5 8 7 13 14 17 16 5 6 9 8 14 15 18 17]: - :CGNS#Distribution: - Element [2 4 4]: - Quad [7 0]: - ElementRange [6]: - FlowSolution: - Density [1.2]: - ZoneBC: - BC_1: - FamilyName "Wall": - PointList [6]: - :CGNS#Distribution: - Index [1 2 2]: - GridLocation "FaceCenter": - BCDataSet: - DirichletData: - Temperature [2.]: diff --git a/doc/introduction/images/trees/generate_unicode_trees.py b/doc/introduction/images/trees/generate_unicode_trees.py deleted file mode 100644 index 1820e7f8..00000000 --- a/doc/introduction/images/trees/generate_unicode_trees.py +++ /dev/null @@ -1,14 +0,0 @@ -from maia.pytree.yaml.pretty_print import pretty_print -from pathlib import Path -import os - -this_script_dir = Path(__file__).resolve().parent - -with open(os.path.join(this_script_dir,"4_cubes_lite.yaml")) as yt: - pretty_print(yt) - -with open(os.path.join(this_script_dir,"4_cubes_lite_dist_0.yaml")) as yt: - pretty_print(yt) - -with open(os.path.join(this_script_dir,"4_cubes_lite_dist_1.yaml")) as yt: - pretty_print(yt) diff --git a/doc/introduction/images/trees/trees.pptx b/doc/introduction/images/trees/trees.pptx deleted file mode 100644 index d30540fb..00000000 Binary files a/doc/introduction/images/trees/trees.pptx and /dev/null differ diff --git a/doc/maia_pytree/images/badge_CI.svg b/doc/maia_pytree/images/badge_CI.svg deleted file mode 100755 index 7fc099b9..00000000 --- a/doc/maia_pytree/images/badge_CI.svg +++ /dev/null @@ -1,35 +0,0 @@ - - CI - passing - - - - - - - - - - - - - - - - - - CI - - - - - - - passing - - - - - - diff --git a/doc/maia_pytree/images/badge_cov.svg b/doc/maia_pytree/images/badge_cov.svg deleted file mode 100755 index 4e9ff4a7..00000000 --- a/doc/maia_pytree/images/badge_cov.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - codecov - codecov - 92% - 92% - - - - - \ No newline at end of file diff --git a/doc/maia_pytree/images/badge_py3.svg b/doc/maia_pytree/images/badge_py3.svg deleted file mode 100755 index 4aee57b5..00000000 --- a/doc/maia_pytree/images/badge_py3.svg +++ /dev/null @@ -1 +0,0 @@ -Python: 3Python3 \ No newline at end of file diff --git a/doc/maia_pytree/images/void_20px.svg b/doc/maia_pytree/images/void_20px.svg deleted file mode 100755 index 3b54ebea..00000000 --- a/doc/maia_pytree/images/void_20px.svg +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - diff --git a/doc/snippets/test_quick_start.py b/doc/snippets/test_quick_start.py deleted file mode 100644 index f971494f..00000000 --- a/doc/snippets/test_quick_start.py +++ /dev/null @@ -1,90 +0,0 @@ -import pytest - -know_cassiopee = True -try: - import Transform.PyTree as CTransform - import Converter.PyTree as CConverter - import Post.PyTree as CPost -except ImportError: - know_cassiopee = False - -@pytest.fixture -def convert_yaml(): - from mpi4py import MPI - import maia.io - from maia.utils.test_utils import mesh_dir - for mesh in ['S_twoblocks', 'U_ATB_45']: - yaml_name = mesh + '.yaml' - cgns_name = mesh + '.cgns' - tree = maia.io.file_to_dist_tree(mesh_dir/yaml_name, MPI.COMM_WORLD) - maia.io.dist_tree_to_file(tree, cgns_name, MPI.COMM_WORLD) - - -def test_basic_algo(convert_yaml): - #basic_algo@start - from mpi4py.MPI import COMM_WORLD as comm - import maia - - tree_s = maia.io.file_to_dist_tree('S_twoblocks.cgns', comm) - tree_u = maia.algo.dist.convert_s_to_ngon(tree_s, comm) - maia.io.dist_tree_to_file(tree_u, 'U_twoblocks.cgns', comm) - #basic_algo@end - -def test_workflow(convert_yaml): - #workflow@start - from mpi4py.MPI import COMM_WORLD as comm - import maia.pytree as PT - import maia - - # Read the file. Tree is distributed - dist_tree = maia.io.file_to_dist_tree('U_ATB_45.cgns', comm) - - # Duplicate the section to a 180° mesh - # and merge all the blocks into one - opposite_jns = [['Base/bump_45/ZoneGridConnectivity/matchA'], - ['Base/bump_45/ZoneGridConnectivity/matchB']] - maia.algo.dist.duplicate_from_periodic_jns(dist_tree, - ['Base/bump_45'], opposite_jns, 22, comm) - maia.algo.dist.merge_connected_zones(dist_tree, comm) - - # Split the mesh to have a partitioned tree - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - # Now we can call some partitioned algorithms - maia.algo.part.compute_wall_distance(part_tree, comm, point_cloud='Vertex') - extract_tree = maia.algo.part.extract_part_from_bc_name(part_tree, "wall", comm) - slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0], comm, - containers_name=['WallDistance']) - - # Merge extractions in a same tree in order to save it - base = PT.get_child_from_label(slice_tree, 'CGNSBase_t') - PT.set_name(base, f'PlaneSlice') - PT.add_child(extract_tree, base) - maia.algo.pe_to_nface(dist_tree,comm) - - extract_tree_dist = maia.factory.recover_dist_tree(extract_tree, comm) - maia.io.dist_tree_to_file(extract_tree_dist, 'ATB_extract.cgns', comm) - #workflow@end - # maia.io.dist_tree_to_file(dist_tree, 'out.cgns', comm) # Write volumic to generate figure - -@pytest.mark.skipif(not know_cassiopee, reason="Require Cassiopee") #For refine_mesh -def test_pycgns(): - #pycgns@start - from mpi4py.MPI import COMM_WORLD as comm - import maia - import Transform.PyTree as CTransform - import Converter.PyTree as CConverter - import Post.PyTree as CPost - - dist_tree = maia.factory.generate_dist_block([101,6,6], 'TETRA_4', comm) - CTransform._scale(dist_tree, [5,1,1], X=(0,0,0)) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - CConverter._initVars(part_tree, '{Field}=sin({nodes:CoordinateX})') - part_tree = CPost.computeGrad(part_tree, 'Field') - - maia.transfer.part_tree_to_dist_tree_all(dist_tree, part_tree, comm) - maia.io.dist_tree_to_file(dist_tree, 'out.cgns', comm) - #pycgns@end - # maia.io.write_trees(part_tree, 'part.cgns', comm) # Write partitions to generate figure - # (crop it h=650) diff --git a/doc/user_manual/_drafts/distributed_tree.rst.draft b/doc/user_manual/_drafts/distributed_tree.rst.draft deleted file mode 100644 index 598b87ad..00000000 --- a/doc/user_manual/_drafts/distributed_tree.rst.draft +++ /dev/null @@ -1,236 +0,0 @@ -.. contents:: :local: - -.. _dist_tree: - -Loading a distributed tree -========================== - -TODO - -A *dist tree* is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes may be distributed. - -The generalized paths of the distributed nodes are listed below. For all other nodes within the tree, the node values are loaded by all processes. - -TODO: make it possible to add node generalized path to the list of node that are distributed -TODO: warn if loading array > 10000 - -TODO move this definition -A *generalized path* is a path where each token of the path is either a node *name* or a node *label*. Example: "Zone_t/Hexa" is a generalized path and a node will match if it is a sub-node of any zone, and if its name is "Hexa". -Note: of course there is no way to distinguish if we mean "of type Zone_t" or "of name Zone_t" so it will match both. - - -.. code:: - - - - - CGNSBase_t - └───Zone_t - ├───GridCoordinates_t - │ └───DataArray_t - │ - ├───Elements_t - │ ├───ElementConnectivity - │ ├───ElementStartOffset - │ ├───ParentElement - │ └───ParentElementPosition [not implemented] - │ - ├───FlowSolution_t - │ ├───DataArray_t - │ └───PointList - │ - ├───DiscreteData_t - │ ├───DataArray_t - │ └───PointList - │ - ├───ZoneBC_t - │ └───BC_t - │ ├───PointList - │ └───BCDataSet_t - │ ├───PointList - │ └───BCData_t - │ └───DataArray_t - │ - ├───ZoneGridConnectivity - │ └───GridConnectivity - │ ├───PointList - │ └───PointListDonor - │ - └───ZoneSubRegion - ├───DataArray_t - └───PointList - - - -For these nodes, array values are distributed across processes. That is, for a dist_tree, on one process, the value of the node is actually a sub-interval array of the whole array. For each node, the sub-interval, plus the total size of the array are given by the triplet `[sub_first_index,sub_last_index,total_size]`. - -The triplet is stored in a node `PartialDistribution` of type `Distribution_t`. Since many arrays are of the same size, they share this distribution, and there is not need to duplicate it for each shared array. `Distribution_t` nodes go here: - -.. code:: - - CGNSBase_t - |___Zone_t - |___**Distribution_t Distribution** - | |___IndexArray_t Vertex - | |___IndexArray_t Cell - | |___IndexArray_t VertexBoundary - | - |___GridCoordinates_t - | |___DataArray_t - | |___**Distribution_t** (optionel) - | |___IndexArray_t Vertex - | - |___Elements_t - | |___ElementConnectivity - | |___ElementStartOffset - | |___ParentElement - | |___ParentElementPosition [not implemented] - | |___**Distribution_t** - | |___IndexArray_t Element - | |___IndexArray_t ElementStartOffset - | - |___FlowSolution_t - | |___DataArray_t - | |___PointList - | |___**Distribution_t** (si PointList et si pas de NFace) - | |___**Distribution_t** - | |___IndexArray_t Vertex|Cell|Face (Face si PointList) - | - |___DiscreteData_t - | |___DataArray_t - | |___PointList - | |___**Distribution_t** (si PointList) - | |___IndexArray_t Vertex|Cell|Face (Face si PointList) - | - |___ZoneBC_t - | |___BC_t - | |___PointList - | |___**Distribution_t** - | |___IndexArray_t Vertex|Cell|Face (Suivant GridLocation) - | |___BCDataSet_t - | |___PointList - | |___IndexArray_t Vertex|Cell|Face (Suivant GridLocation) - | |___**Distribution_t** - | |___BCData_t - | |___DataArray_t - | - |___ZoneGridConnectivity - | |___GridConnectivity - | |___PointList - | |___PointListDonor - | |___**Distribution_t** - | |___IndexArray_t Vertex|Cell|Face (Suivant GridLocation) - | - |___ZoneSubRegion [not implemented] - - -.. code:: - - CGNSBase_t - |___Zone_t - |___**GlobalNumbering** - | |___Vertex - | |___Cell - | |___CellBoundary - | - |___GridCoordinates_t - | |___DataArray_t - | |___**GlobalNumbering** - | - |___Elements_t - | |___ElementConnectivity - | |___ElementStartOffset - | |___ParentElement - | |___ParentElementPosition [not implemented] - | |___**GlobalNumbering** - | - |___FlowSolution_t - | |___DataArray_t - | |___PointList - | |___**GlobalNumbering** (si PointList) - | - |___DiscreteData_t - | |___DataArray_t - | |___PointList - | |___**GlobalNumbering** (si PointList) - | - |___ZoneBC_t - | |___BC_t - | |___PointList - | |___**GlobalNumbering** - | |___BCDataSet_t - | |___PointList - | |___**GlobalNumbering** - | |___BCData_t - | |___DataArray_t - | - |___ZoneGridConnectivity - | |___GridConnectivity - | |___PointList - | |___PointListDonor - | |___**GlobalNumbering** - | - |___ZoneSubRegion [not implemented] - - -Elements_t ----------- - -For heterogenous connectivities, the :code:`ElementStartOffset` and :code:`ElementConnectivity` arrays are not independent. The :code:`ElementStartPartialDistribution` refers to the :code:`ElementStartOffset` array (actually, the :code:`ElementStartOffset` load one more element), and the partial :code:`ElementConnectivity` block loaded by one process is the one described by the :code:`ElementStartOffset` block of that process. - -TODO -ElementStartPartialDistribution -ElementConnectivityPartialDistribution - -Example -------- - -Let us look at this tree: - -.. code:: yaml - - Base0 Base_t [3,3]: - Zone0 Zone_t [[24],[6],[0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t [0,1,2,3,4,5,6]: - Polygons Elements_t: - ElementStartOffset DataArray_t [0,4,8]: - ElementConnectivity DataArray_t [4,3,2,1, 1,5,6,7]: - -TODO ajouter 2 BCs - -If it is distributed on two processes, the dist_tree of each process will be: - -.. code:: yaml - - Base0 Base_t [3,3]: - Zone0 Zone_t [[24],[6],[0]]: - GridCoordinates GridCoordinates_t: - PartialDistribution Distribution_t [0,4,7]: # the block array contains data - # from sub-interval [0,3) and the array total size is 7 - CoordinateX DataArray_t [0,1,2,3]: - Polygons Elements_t: - ElementStartPartialDistribution Distribution_t [0,2,3]: # the block array contains connectivities [0,1) (i.e. only 0) - ElementStartOffset DataArray_t [0,4]: # in the global array, the connectivity starts at 0 and finishes at 4 - ElementConnectivity DataArray_t [4,3,2,1]: # this is connectivity 0 - - Base0 Base_t [3,3]: - Zone0 Zone_t [[24],[6],[0]]: - GridCoordinates GridCoordinates_t: - PartialDistribution Distribution_t [4,7,7]: - CoordinateX DataArray_t [4,5,6]: - Polygons Elements_t: - ElementStartPartialDistribution Distribution_t [1,3,3]: # the block array contains connectivities [1,2) (i.e. only 1) - ElementConnectivityPartialDistribution Distribution_t [4,8,8]: # the block array contains connectivities [1,2) (i.e. only 1) - ElementStartOffset DataArray_t [4,8]: # in the global array, the connectivity starts at 4 and finishes at 8 - ElementConnectivity DataArray_t [1,5,6,7]: # this is connectivity 1 - - - Hexa Quad Tet - - LN_to_GN elements - LN_to_GN cell - LN_to_GN faces - - FlowSolution Hexa Tet - |0 10|10 20| diff --git a/doc/user_manual/images/workflow.pptx b/doc/user_manual/images/workflow.pptx deleted file mode 100755 index 2a43671c..00000000 Binary files a/doc/user_manual/images/workflow.pptx and /dev/null differ diff --git a/doc/user_manual/snippets/test_algo.py b/doc/user_manual/snippets/test_algo.py deleted file mode 100644 index b7ad4836..00000000 --- a/doc/user_manual/snippets/test_algo.py +++ /dev/null @@ -1,548 +0,0 @@ -import pytest -import shutil - -feflo_exists = shutil.which('feflo.a') is not None - -def test_convert_s_to_u(): - #convert_s_to_u@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD) - - maia.algo.dist.convert_s_to_u(dist_tree, 'NGON_n', MPI.COMM_WORLD) - for zone in maia.pytree.get_all_Zone_t(dist_tree): - assert maia.pytree.Zone.Type(zone) == "Unstructured" - #convert_s_to_u@end - -def test_transform_affine(): - #transform_affine@start - from mpi4py import MPI - import maia - dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD) - zone = maia.pytree.get_all_Zone_t(dist_tree)[0] - - maia.algo.transform_affine(zone, translation=[3,0,0]) - #transform_affine@end - -def test_scale_mesh(): - #scale_mesh@start - from mpi4py import MPI - import maia - dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD) - - assert maia.pytree.get_node_from_name(dist_tree, 'CoordinateX')[1].max() <= 1. - maia.algo.scale_mesh(dist_tree, [3.0, 2.0, 1.0]) - assert maia.pytree.get_node_from_name(dist_tree, 'CoordinateX')[1].max() <= 3. - #scale_mesh@end - -def test_generate_jns_vertex_list(): - #generate_jns_vertex_list@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - dist_tree_s = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD) - dist_tree = maia.algo.dist.convert_s_to_ngon(dist_tree_s, MPI.COMM_WORLD) - - maia.algo.dist.generate_jns_vertex_list(dist_tree, MPI.COMM_WORLD) - assert len(maia.pytree.get_nodes_from_name(dist_tree, 'match*#Vtx')) == 2 - #generate_jns_vertex_list@end - -def test_merge_zones(): - #merge_zones@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD) - assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 3 - - maia.algo.dist.merge_zones(dist_tree, ["BaseA/blk1", "BaseB/blk2"], MPI.COMM_WORLD) - assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 2 - #merge_zones@end - -def test_merge_zones_from_family(): - #merge_zones_from_family@start - from mpi4py import MPI - import maia - import maia.pytree as PT - from maia.utils.test_utils import mesh_dir - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD) - - # FamilyName are not included in the mesh - for zone in PT.get_all_Zone_t(dist_tree): - PT.new_child(zone, 'FamilyName', 'FamilyName_t', 'Naca0012') - - maia.algo.dist.merge_zones_from_family(dist_tree, 'Naca0012', MPI.COMM_WORLD) - - zones = PT.get_all_Zone_t(dist_tree) - assert len(zones) == 1 and PT.get_name(zones[0]) == 'naca0012' - #merge_zones_from_family@end - -def test_merge_connected_zones(): - #merge_connected_zones@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD) - - maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD) - assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 1 - #merge_connected_zones@end - -def test_compute_cell_center(): - #compute_cell_center@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - for zone in maia.pytree.iter_all_Zone_t(part_tree): - cell_center = maia.algo.part.compute_cell_center(zone) - #compute_cell_center@end - -def test_compute_face_center(): - #compute_face_center@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - for zone in maia.pytree.iter_all_Zone_t(part_tree): - face_center = maia.algo.part.compute_face_center(zone) - #compute_face_center@end - -def test_compute_edge_center(): - #compute_edge_center@start - from mpi4py import MPI - import maia - dist_tree = maia.factory.generate_dist_block(10, "QUAD_4", MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - for zone in maia.pytree.iter_all_Zone_t(part_tree): - edge_center = maia.algo.part.geometry.compute_edge_center(zone) - #compute_edge_center@end - -def test_compute_wall_distance(): - #compute_wall_distance@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD) - assert maia.pytree.get_node_from_name(part_tree, "WallDistance") is not None - #compute_wall_distance@end - -def test_compute_iso_surface(): - #compute_iso_surface@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex') - - part_tree_iso = maia.algo.part.iso_surface(part_tree, "WallDistance/TurbulentDistance", iso_val=0.25,\ - containers_name=['WallDistance'], comm=MPI.COMM_WORLD) - - assert maia.pytree.get_node_from_name(part_tree_iso, "WallDistance") is not None - #compute_iso_surface@end - -def test_compute_plane_slice(): - #compute_plane_slice@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD) - maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD) # Isosurf requires single block mesh - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0.5], MPI.COMM_WORLD, elt_type='QUAD_4') - #compute_plane_slice@end - -def test_compute_spherical_slice(): - #compute_spherical_slice@start - from mpi4py import MPI - import numpy - import maia - import maia.pytree as PT - dist_tree = maia.factory.generate_dist_block(11, 'Poly', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD, preserve_orientation=True) - - # Add solution - zone = PT.get_node_from_label(part_tree, "Zone_t") - vol_rank = MPI.COMM_WORLD.Get_rank() * numpy.ones(PT.Zone.n_cell(zone)) - src_sol = PT.new_FlowSolution('FlowSolution', loc='CellCenter', fields={'i_rank' : vol_rank}, parent=zone) - - slice_tree = maia.algo.part.spherical_slice(part_tree, [0.5,0.5,0.5,0.25], MPI.COMM_WORLD, \ - ["FlowSolution"], elt_type="NGON_n") - - assert maia.pytree.get_node_from_name(slice_tree, "FlowSolution") is not None - #compute_spherical_slice@end - -def test_extract_from_zsr(): - #extract_from_zsr@start - from mpi4py import MPI - import numpy as np - import maia - import maia.pytree as PT - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex') - - # Create a ZoneSubRegion on procs for extracting odd cells - for part_zone in PT.get_all_Zone_t(part_tree): - ncell = PT.Zone.n_cell(part_zone) - start_range = PT.Element.Range(PT.Zone.NFaceNode(part_zone))[0] - point_list = np.arange(start_range, start_range+ncell, 2, dtype=np.int32).reshape((1,-1), order='F') - PT.new_ZoneSubRegion(name='ZoneSubRegion', point_list=point_list, loc='CellCenter', parent=part_zone) - - extracted_tree = maia.algo.part.extract_part_from_zsr(part_tree, 'ZoneSubRegion', MPI.COMM_WORLD, - containers_name=["WallDistance"]) - - assert maia.pytree.get_node_from_name(extracted_tree, "WallDistance") is not None - #extract_from_zsr@end - -def test_extract_from_bc_name(): - #extract_from_bc_name@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex') - - extracted_bc = maia.algo.part.extract_part_from_bc_name(part_tree, \ - 'wall', MPI.COMM_WORLD, containers_name=["WallDistance"]) - - assert maia.pytree.get_node_from_name(extracted_bc, "WallDistance") is not None - #extract_from_bc_name@end - -def test_extract_from_family(): - #extract_from_family@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex') - - extracted_bc = maia.algo.part.extract_part_from_family(part_tree, \ - 'WALL', MPI.COMM_WORLD, containers_name=["WallDistance"]) - - assert maia.pytree.get_node_from_name(extracted_bc, "WallDistance") is not None - #extract_from_family@end - -def test_compute_elliptical_slice(): - #compute_elliptical_slice@start - from mpi4py import MPI - import maia - import maia.pytree as PT - from maia.algo.part import isosurf - dist_tree = maia.factory.generate_dist_block(11, 'Poly', MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD, preserve_orientation=True) - - slice_tree = isosurf.elliptical_slice(part_tree, [0.5,0.5,0.5,.5,1.,1.,.25**2], \ - MPI.COMM_WORLD, elt_type='NGON_n') - #compute_elliptical_slice@end - -def test_localize_points(): - #localize_points@start - import mpi4py - import maia - import maia.pytree as PT - from maia.utils.test_utils import mesh_dir - comm = mpi4py.MPI.COMM_WORLD - - dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm) - dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm) - for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt): - maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3]) - part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm) - part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm) - - maia.algo.part.localize_points(part_tree_src, part_tree_tgt, 'CellCenter', comm) - for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt): - loc_container = PT.get_child_from_name(tgt_zone, 'Localization') - assert PT.Subset.GridLocation(loc_container) == 'CellCenter' - #localize_points@end - -def test_find_closest_points(): - #find_closest_points@start - import mpi4py - import maia - import maia.pytree as PT - from maia.utils.test_utils import mesh_dir - comm = mpi4py.MPI.COMM_WORLD - - dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm) - dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm) - for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt): - maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3]) - part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm) - part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm) - - maia.algo.part.find_closest_points(part_tree_src, part_tree_tgt, 'Vertex', comm) - for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt): - loc_container = PT.get_child_from_name(tgt_zone, 'ClosestPoint') - assert PT.Subset.GridLocation(loc_container) == 'Vertex' - #find_closest_points@end - -def test_interpolate_from_part_trees(): - #interpolate_from_part_trees@start - import mpi4py - import numpy - import maia - import maia.pytree as PT - comm = mpi4py.MPI.COMM_WORLD - - dist_tree_src = maia.factory.generate_dist_block(11, 'Poly', comm) - dist_tree_tgt = maia.factory.generate_dist_block(20, 'Poly', comm) - part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm) - part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm) - # Create fake solution - zone = maia.pytree.get_node_from_label(part_tree_src, "Zone_t") - src_sol = maia.pytree.new_FlowSolution('FlowSolution', loc='CellCenter', parent=zone) - PT.new_DataArray("Field", numpy.random.rand(PT.Zone.n_cell(zone)), parent=src_sol) - - maia.algo.part.interpolate_from_part_trees(part_tree_src, part_tree_tgt, comm,\ - ['FlowSolution'], 'Vertex') - tgt_sol = PT.get_node_from_name(part_tree_tgt, 'FlowSolution') - assert tgt_sol is not None and PT.Subset.GridLocation(tgt_sol) == 'Vertex' - #interpolate_from_part_trees@end - -def test_centers_to_nodes(): - #centers_to_nodes@start - import mpi4py - import maia - import maia.pytree as PT - from maia.utils.test_utils import mesh_dir - comm = mpi4py.MPI.COMM_WORLD - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - # Init a FlowSolution located at Cells - for part in PT.get_all_Zone_t(part_tree): - cell_center = maia.algo.part.compute_cell_center(part) - fields = {'ccX': cell_center[0::3], 'ccY': cell_center[1::3], 'ccZ': cell_center[2::3]} - PT.new_FlowSolution('FSol', loc='CellCenter', fields=fields, parent=part) - - maia.algo.part.centers_to_nodes(part_tree, comm, ['FSol']) - - for part in PT.get_all_Zone_t(part_tree): - vtx_sol = PT.get_node_from_name(part, 'FSol#Vtx') - assert PT.Subset.GridLocation(vtx_sol) == 'Vertex' - #centers_to_nodes@end - -def test_nodes_to_centers(): - #nodes_to_centers@start - import mpi4py - import maia - import maia.pytree as PT - comm = mpi4py.MPI.COMM_WORLD - - dist_tree = maia.factory.generate_dist_sphere(3, 'TETRA_4', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - # Init a FlowSolution located at Nodes - for part in PT.get_all_Zone_t(part_tree): - cx, cy, cz = PT.Zone.coordinates(part) - fields = {'cX': cx, 'cY': cy, 'cZ': cz} - PT.new_FlowSolution('FSol', loc='Vertex', fields=fields, parent=part) - - maia.algo.part.nodes_to_centers(part_tree, comm, ['FSol']) - - for part in PT.get_all_Zone_t(part_tree): - cell_sol = PT.get_node_from_name(part, 'FSol#Cell') - assert PT.Subset.GridLocation(cell_sol) == 'CellCenter' - #nodes_to_centers@end - -def test_pe_to_nface(): - #pe_to_nface@start - from mpi4py import MPI - import maia - tree = maia.factory.generate_dist_block(6, 'Poly', MPI.COMM_WORLD) - - for zone in maia.pytree.get_all_Zone_t(tree): - maia.algo.pe_to_nface(zone, MPI.COMM_WORLD) - assert maia.pytree.get_child_from_name(zone, 'NFaceElements') is not None - #pe_to_nface@end - -def test_nface_to_pe(): - #nface_to_pe@start - from mpi4py import MPI - import maia - tree = maia.factory.generate_dist_block(6, 'NFace_n', MPI.COMM_WORLD) - - maia.algo.nface_to_pe(tree, MPI.COMM_WORLD) - assert maia.pytree.get_node_from_name(tree, 'ParentElements') is not None - #nface_to_pe@end - -def test_poly_new_to_old(): - #poly_new_to_old@start - import maia - from maia.utils.test_utils import mesh_dir - - tree = maia.io.read_tree(mesh_dir/'U_ATB_45.yaml') - assert maia.pytree.get_node_from_name(tree, 'ElementStartOffset') is not None - - maia.algo.seq.poly_new_to_old(tree) - assert maia.pytree.get_node_from_name(tree, 'ElementStartOffset') is None - #poly_new_to_old@end - -def test_poly_old_to_new(): - #poly_old_to_new@start - import maia - from maia.utils.test_utils import mesh_dir - - tree = maia.io.read_tree(mesh_dir/'U_ATB_45.yaml') - maia.algo.seq.poly_new_to_old(tree) - assert maia.pytree.get_node_from_name(tree, 'ElementStartOffset') is None - - maia.algo.seq.poly_old_to_new(tree) - assert maia.pytree.get_node_from_name(tree, 'ElementStartOffset') is not None - #poly_old_to_new@end - -def test_enforce_ngon_pe_local(): - #enforce_ngon_pe_local@start - from mpi4py import MPI - import maia - import maia.pytree as PT - - tree = maia.factory.generate_dist_block(11, 'Poly', MPI.COMM_WORLD) - zone = PT.get_node_from_label(tree, 'Zone_t') - n_cell = PT.Zone.n_cell(zone) - - assert PT.get_node_from_name(zone, 'ParentElements')[1].max() > n_cell - maia.algo.seq.enforce_ngon_pe_local(tree) - assert PT.get_node_from_name(zone, 'ParentElements')[1].max() <= n_cell - #enforce_ngon_pe_local@end - -def test_elements_to_ngons(): - #elements_to_ngons@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD) - maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD, stable_sort=True) - #elements_to_ngons@end - -def test_convert_elements_to_ngon(): - #convert_elements_to_ngon@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD) - maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD) - #convert_elements_to_ngon@end - -def test_convert_elements_to_mixed(): - #convert_elements_to_mixed@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD) - maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD) - #convert_elements_to_mixed@end - -def test_convert_mixed_to_elements(): - #convert_mixed_to_elements@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD) - maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD) - maia.algo.dist.convert_mixed_to_elements(dist_tree, MPI.COMM_WORLD) - #convert_mixed_to_elements@end - -def test_rearrange_element_sections(): - #rearrange_element_sections@start - from mpi4py import MPI - import maia - import maia.pytree as PT - - dist_tree = maia.factory.generate_dist_block(11, 'PYRA_5', MPI.COMM_WORLD) - pyras = PT.get_node_from_name(dist_tree, 'PYRA_5.0') - assert PT.Element.Range(pyras)[0] == 1 #Until now 3D elements are first - - maia.algo.dist.rearrange_element_sections(dist_tree, MPI.COMM_WORLD) - tris = PT.get_node_from_name(dist_tree, 'TRI_3') #Now 2D elements are first - assert PT.Element.Range(tris)[0] == 1 - #rearrange_element_sections@end - -def test_recover1to1(): - #recover1to1@start - from mpi4py import MPI - from numpy import array, pi - import maia - import maia.pytree as PT - from maia.utils.test_utils import mesh_dir - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD) - - # Remove data that should be created - PT.rm_nodes_from_name(dist_tree, 'PointListDonor') - PT.rm_nodes_from_name(dist_tree, 'GridConnectivityProperty') - - # Create FamilyName on interface nodes - PT.new_node('FamilyName', 'FamilyName_t', 'Side1', - parent=PT.get_node_from_name(dist_tree, 'matchA')) - PT.new_node('FamilyName', 'FamilyName_t', 'Side2', - parent=PT.get_node_from_name(dist_tree, 'matchB')) - - maia.algo.dist.connect_1to1_families(dist_tree, ('Side1', 'Side2'), MPI.COMM_WORLD, - periodic={'rotation_angle' : array([-2*pi/45.,0.,0.])}) - - assert len(PT.get_nodes_from_name(dist_tree, 'PointListDonor')) == 2 - #recover1to1@end - -def test_redistribute_dist_tree(): - #redistribute_dist_tree@start - from mpi4py import MPI - import maia - - dist_tree_ini = maia.factory.generate_dist_block(21, 'Poly', MPI.COMM_WORLD) - dist_tree_gathered = maia.algo.dist.redistribute_tree(dist_tree_ini, \ - 'gather.0', MPI.COMM_WORLD) - #redistribute_dist_tree@end - -@pytest.mark.skipif(not feflo_exists, reason="Require Feflo.a") -def test_adapt_with_feflo(): - #adapt_with_feflo@start - import mpi4py.MPI as MPI - import maia - import maia.pytree as PT - - from maia.algo.dist import adapt_mesh_with_feflo - - dist_tree = maia.factory.generate_dist_block(5, 'TETRA_4', MPI.COMM_WORLD) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - - # > Create a metric field - cx, cy, cz = PT.Zone.coordinates(zone) - fields= {'metric' : (cx-0.5)**5+(cy-0.5)**5 - 1} - PT.new_FlowSolution("FlowSolution", loc="Vertex", fields=fields, parent=zone) - - # > Adapt mesh according to scalar metric - adpt_dist_tree = adapt_mesh_with_feflo(dist_tree, - "FlowSolution/metric", - MPI.COMM_WORLD, - container_names=["FlowSolution"], - feflo_opts="-c 100 -cmax 100 -p 4") - #adapt_with_feflo@end diff --git a/doc/user_manual/snippets/test_factory.py b/doc/user_manual/snippets/test_factory.py deleted file mode 100644 index 6a41fa58..00000000 --- a/doc/user_manual/snippets/test_factory.py +++ /dev/null @@ -1,145 +0,0 @@ -def test_generate_dist_points(): - #generate_dist_points@start - from mpi4py import MPI - import maia - import maia.pytree as PT - - dist_tree = maia.factory.generate_dist_points(10, 'Unstructured', MPI.COMM_WORLD) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - assert PT.Zone.n_vtx(zone) == 10**3 - #generate_dist_points@end - -def test_generate_dist_block(): - #generate_dist_block@start - from mpi4py import MPI - import maia - import maia.pytree as PT - - dist_tree = maia.factory.generate_dist_block([10,20,10], 'Structured', MPI.COMM_WORLD) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - assert PT.Zone.Type(zone) == "Structured" - - dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - assert PT.Element.CGNSName(PT.get_child_from_label(zone, 'Elements_t')) == 'NGON_n' - - dist_tree = maia.factory.generate_dist_block(10, 'TETRA_4', MPI.COMM_WORLD) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - assert PT.Element.CGNSName(PT.get_child_from_label(zone, 'Elements_t')) == 'TETRA_4' - #generate_dist_block@end - -def test_generate_dist_sphere(): - #generate_dist_sphere@start - from mpi4py import MPI - import maia - import maia.pytree as PT - - dist_tree = maia.factory.generate_dist_sphere(10, 'TRI_3', MPI.COMM_WORLD) - assert PT.Element.CGNSName(PT.get_node_from_label(dist_tree, 'Elements_t')) == 'TRI_3' - #generate_dist_sphere@end - -def test_full_to_dist_tree(): - #full_to_dist_tree@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - comm = MPI.COMM_WORLD - - if comm.Get_rank() == 0: - tree = maia.io.read_tree(mesh_dir/'S_twoblocks.yaml', comm) - else: - tree = None - dist_tree = maia.factory.full_to_dist_tree(tree, comm, owner=0) - #full_to_dist_tree@end - -def test_dist_to_full_tree(): - #dist_to_full_tree@start - from mpi4py import MPI - import maia - comm = MPI.COMM_WORLD - - dist_tree = maia.factory.generate_dist_sphere(10, 'TRI_3', comm) - full_tree = maia.factory.dist_to_full_tree(dist_tree, comm, target=0) - - if comm.Get_rank() == 0: - assert maia.pytree.get_node_from_name(full_tree, ":CGNS#Distribution") is None - else: - assert full_tree is None - #dist_to_full_tree@end - -def test_partition_dist_tree(): - #partition_dist_tree@start - from mpi4py import MPI - import maia - - comm = MPI.COMM_WORLD - i_rank, n_rank = comm.Get_rank(), comm.Get_size() - dist_tree = maia.factory.generate_dist_block(10, 'Poly', comm) - - #Basic use - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - #Crazy partitioning where each proc get as many partitions as its rank - n_part_tot = n_rank * (n_rank + 1) // 2 - part_tree = maia.factory.partition_dist_tree(dist_tree, comm, \ - zone_to_parts={'Base/zone' : [1./n_part_tot for i in range(i_rank+1)]}) - assert len(maia.pytree.get_all_Zone_t(part_tree)) == i_rank+1 - #partition_dist_tree@end - -def test_compute_nosplit_weights(): - #compute_nosplit_weights@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - from maia.factory import partitioning as mpart - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD) - - zone_to_parts = mpart.compute_nosplit_weights(dist_tree, MPI.COMM_WORLD) - if MPI.COMM_WORLD.Get_size() == 2: - assert len(zone_to_parts) == 1 - #compute_nosplit_weights@end - -def test_compute_regular_weights(): - #compute_regular_weights@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - from maia.factory import partitioning as mpart - - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD) - - zone_to_parts = mpart.compute_regular_weights(dist_tree, MPI.COMM_WORLD) - if MPI.COMM_WORLD.Get_size() == 2: - assert zone_to_parts == {'Base/Large': [0.5], 'Base/Small': [0.5]} - #compute_regular_weights@end - -def test_compute_balanced_weights(): - #compute_balanced_weights@start - from mpi4py import MPI - import maia - from maia.utils.test_utils import mesh_dir - from maia.factory import partitioning as mpart - - comm = MPI.COMM_WORLD - dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', comm) - - zone_to_parts = mpart.compute_balanced_weights(dist_tree, comm) - if comm.Get_size() == 2 and comm.Get_rank() == 0: - assert zone_to_parts == {'Base/Large': [0.375], 'Base/Small': [1.0]} - if comm.Get_size() == 2 and comm.Get_rank() == 1: - assert zone_to_parts == {'Base/Large': [0.625]} - #compute_balanced_weights@end - -def test_recover_dist_tree(): - #recover_dist_tree@start - from mpi4py import MPI - import maia - comm = MPI.COMM_WORLD - - dist_tree_bck = maia.factory.generate_dist_block(5, 'TETRA_4', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree_bck, comm) - - dist_tree = maia.factory.recover_dist_tree(part_tree, comm) - assert maia.pytree.is_same_tree(dist_tree, dist_tree_bck) - #recover_dist_tree@end diff --git a/doc/user_manual/snippets/test_io.py b/doc/user_manual/snippets/test_io.py deleted file mode 100644 index bd00eb23..00000000 --- a/doc/user_manual/snippets/test_io.py +++ /dev/null @@ -1,65 +0,0 @@ -def test_file_to_dist_tree_full(): - #file_to_dist_tree_full@start - from mpi4py import MPI - import maia - - # Generate a sample tree - dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD) - # Write - maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD) - # Read - tree = maia.io.file_to_dist_tree("tree.cgns", MPI.COMM_WORLD) - #file_to_dist_tree_full@end - -def test_file_to_dist_tree_filter(): - #file_to_dist_tree_filter@start - from mpi4py import MPI - import maia - - # Generate a sample tree - dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD) - - # Remove the nodes we do not want to write - maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateZ') #This is a DataArray - maia.pytree.rm_nodes_from_name(dist_tree, 'Zm*') #This is some BC nodes - # Write - maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD) - - # Read - from maia.io.cgns_io_tree import load_size_tree, fill_size_tree - dist_tree = load_size_tree("tree.cgns", MPI.COMM_WORLD) - #For now dist_tree only contains sizes : let's filter it - maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateY') #This is a DataArray - maia.pytree.rm_nodes_from_name(dist_tree, 'Ym*') #This is some BC nodes - fill_size_tree(dist_tree, "tree.cgns", MPI.COMM_WORLD) - #file_to_dist_tree_filter@end - -def test_save_part_tree(): - #save_part_tree@start - from mpi4py import MPI - import maia - - dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - - maia.io.part_tree_to_file(part_tree, 'part_tree.cgns', MPI.COMM_WORLD) - #save_part_tree@end - -def test_write_tree(): - #write_tree@start - from mpi4py import MPI - import maia - - dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD) - if MPI.COMM_WORLD.Get_rank() == 0: - maia.io.write_tree(dist_tree, "tree.cgns") - #write_tree@end - -def test_write_trees(): - #write_trees@start - from mpi4py import MPI - import maia - - dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD) - maia.io.write_trees(dist_tree, "tree.cgns", MPI.COMM_WORLD) - #write_trees@end diff --git a/doc/user_manual/snippets/test_transfer.py b/doc/user_manual/snippets/test_transfer.py deleted file mode 100644 index 78799c9b..00000000 --- a/doc/user_manual/snippets/test_transfer.py +++ /dev/null @@ -1,96 +0,0 @@ -def test_dist_zone_to_part_zones_only(): - #dist_zone_to_part_zones_only@start - from mpi4py import MPI - import os - import maia - import maia.pytree as PT - from maia.utils.test_utils import sample_mesh_dir - - comm = MPI.COMM_WORLD - filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml') - - dist_tree = maia.io.file_to_dist_tree(filename, comm) - dist_zone = PT.get_all_Zone_t(dist_tree)[0] - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - part_zones = PT.get_all_Zone_t(part_tree) - - include_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'], - 'BCDataSet_t' : ['*']} - maia.transfer.dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict) - - for zone in part_zones: - assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None - assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is None - assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None - #dist_zone_to_part_zones_only@end - -def test_dist_zone_to_part_zones_all(): - #dist_zone_to_part_zones_all@start - from mpi4py import MPI - import os - import maia - import maia.pytree as PT - from maia.utils.test_utils import sample_mesh_dir - - comm = MPI.COMM_WORLD - filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml') - - dist_tree = maia.io.file_to_dist_tree(filename, comm) - dist_zone = PT.get_all_Zone_t(dist_tree)[0] - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - part_zones = PT.get_all_Zone_t(part_tree) - - exclude_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'], - 'BCDataSet_t' : ['*']} - maia.transfer.dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict) - - for zone in part_zones: - assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is None - assert PT.get_node_from_path(zone, 'FlowSolution/DataY') is not None - assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None - assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None - #dist_zone_to_part_zones_all@end - -def test_dist_tree_to_part_tree_all(): - #dist_tree_to_part_tree_all@start - from mpi4py import MPI - import os - import maia - import maia.pytree as PT - from maia.utils.test_utils import sample_mesh_dir - - filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml') - dist_tree = maia.io.file_to_dist_tree(filename, MPI.COMM_WORLD) - part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD) - maia.transfer.dist_tree_to_part_tree_all(dist_tree, part_tree, MPI.COMM_WORLD) - - zone = PT.get_all_Zone_t(part_tree)[0] - assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None - assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None - assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None - #dist_tree_to_part_tree_all@end - -def test_dist_tree_to_part_tree_only_labels(): - #dist_tree_to_part_tree_only_labels@start - from mpi4py import MPI - import os - import maia - import maia.pytree as PT - from maia.utils.test_utils import sample_mesh_dir - - comm = MPI.COMM_WORLD - filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml') - - dist_tree = maia.io.file_to_dist_tree(filename, comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['FlowSolution_t', 'ZoneSubRegion_t'], comm) - - zone = PT.get_all_Zone_t(part_tree)[0] - assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None - assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None - assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None - - maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['BCDataSet_t'], comm) - assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None - #dist_tree_to_part_tree_only_labels@end - diff --git a/maia/py.typed b/docs/.nojekyll similarity index 100% rename from maia/py.typed rename to docs/.nojekyll diff --git a/docs/1.0/.buildinfo b/docs/1.0/.buildinfo new file mode 100644 index 00000000..4dfb14c0 --- /dev/null +++ b/docs/1.0/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: fcc3992b4482dd2a36c2fd169fbb7924 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/1.0/.doctrees/developer_manual/algo_description.doctree b/docs/1.0/.doctrees/developer_manual/algo_description.doctree new file mode 100644 index 00000000..ae355173 Binary files /dev/null and b/docs/1.0/.doctrees/developer_manual/algo_description.doctree differ diff --git a/docs/1.0/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree b/docs/1.0/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree new file mode 100644 index 00000000..b4a2ba55 Binary files /dev/null and b/docs/1.0/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree differ diff --git a/docs/1.0/.doctrees/developer_manual/developer_manual.doctree b/docs/1.0/.doctrees/developer_manual/developer_manual.doctree new file mode 100644 index 00000000..9727f928 Binary files /dev/null and b/docs/1.0/.doctrees/developer_manual/developer_manual.doctree differ diff --git a/docs/1.0/.doctrees/developer_manual/logging.doctree b/docs/1.0/.doctrees/developer_manual/logging.doctree new file mode 100644 index 00000000..be96deb2 Binary files /dev/null and b/docs/1.0/.doctrees/developer_manual/logging.doctree differ diff --git a/docs/1.0/.doctrees/developer_manual/maia_dev/conventions.doctree b/docs/1.0/.doctrees/developer_manual/maia_dev/conventions.doctree new file mode 100644 index 00000000..ce9c34f6 Binary files /dev/null and b/docs/1.0/.doctrees/developer_manual/maia_dev/conventions.doctree differ diff --git a/docs/1.0/.doctrees/developer_manual/maia_dev/development_workflow.doctree b/docs/1.0/.doctrees/developer_manual/maia_dev/development_workflow.doctree new file mode 100644 index 00000000..731aaaf2 Binary files /dev/null and b/docs/1.0/.doctrees/developer_manual/maia_dev/development_workflow.doctree differ diff --git a/docs/1.0/.doctrees/environment.pickle b/docs/1.0/.doctrees/environment.pickle new file mode 100644 index 00000000..01862222 Binary files /dev/null and b/docs/1.0/.doctrees/environment.pickle differ diff --git a/docs/1.0/.doctrees/index.doctree b/docs/1.0/.doctrees/index.doctree new file mode 100644 index 00000000..70dbe376 Binary files /dev/null and b/docs/1.0/.doctrees/index.doctree differ diff --git a/docs/1.0/.doctrees/installation.doctree b/docs/1.0/.doctrees/installation.doctree new file mode 100644 index 00000000..6f416c11 Binary files /dev/null and b/docs/1.0/.doctrees/installation.doctree differ diff --git a/docs/1.0/.doctrees/introduction/introduction.doctree b/docs/1.0/.doctrees/introduction/introduction.doctree new file mode 100644 index 00000000..0f2337d1 Binary files /dev/null and b/docs/1.0/.doctrees/introduction/introduction.doctree differ diff --git a/docs/1.0/.doctrees/license.doctree b/docs/1.0/.doctrees/license.doctree new file mode 100644 index 00000000..09d331ba Binary files /dev/null and b/docs/1.0/.doctrees/license.doctree differ diff --git a/docs/1.0/.doctrees/quick_start.doctree b/docs/1.0/.doctrees/quick_start.doctree new file mode 100644 index 00000000..fa171444 Binary files /dev/null and b/docs/1.0/.doctrees/quick_start.doctree differ diff --git a/docs/1.0/.doctrees/related_projects.doctree b/docs/1.0/.doctrees/related_projects.doctree new file mode 100644 index 00000000..daf69886 Binary files /dev/null and b/docs/1.0/.doctrees/related_projects.doctree differ diff --git a/docs/1.0/.doctrees/releases/release_notes.doctree b/docs/1.0/.doctrees/releases/release_notes.doctree new file mode 100644 index 00000000..e9146e6f Binary files /dev/null and b/docs/1.0/.doctrees/releases/release_notes.doctree differ diff --git a/docs/1.0/.doctrees/releases/v0.1.0.doctree b/docs/1.0/.doctrees/releases/v0.1.0.doctree new file mode 100644 index 00000000..2ec618c4 Binary files /dev/null and b/docs/1.0/.doctrees/releases/v0.1.0.doctree differ diff --git a/docs/1.0/.doctrees/user_manual/algo.doctree b/docs/1.0/.doctrees/user_manual/algo.doctree new file mode 100644 index 00000000..8cbeeb73 Binary files /dev/null and b/docs/1.0/.doctrees/user_manual/algo.doctree differ diff --git a/docs/1.0/.doctrees/user_manual/config.doctree b/docs/1.0/.doctrees/user_manual/config.doctree new file mode 100644 index 00000000..0dda8b2d Binary files /dev/null and b/docs/1.0/.doctrees/user_manual/config.doctree differ diff --git a/docs/1.0/.doctrees/user_manual/factory.doctree b/docs/1.0/.doctrees/user_manual/factory.doctree new file mode 100644 index 00000000..6de0bbc4 Binary files /dev/null and b/docs/1.0/.doctrees/user_manual/factory.doctree differ diff --git a/docs/1.0/.doctrees/user_manual/io.doctree b/docs/1.0/.doctrees/user_manual/io.doctree new file mode 100644 index 00000000..e017da2d Binary files /dev/null and b/docs/1.0/.doctrees/user_manual/io.doctree differ diff --git a/docs/1.0/.doctrees/user_manual/transfer.doctree b/docs/1.0/.doctrees/user_manual/transfer.doctree new file mode 100644 index 00000000..096345f7 Binary files /dev/null and b/docs/1.0/.doctrees/user_manual/transfer.doctree differ diff --git a/docs/1.0/.doctrees/user_manual/user_manual.doctree b/docs/1.0/.doctrees/user_manual/user_manual.doctree new file mode 100644 index 00000000..c9a6900a Binary files /dev/null and b/docs/1.0/.doctrees/user_manual/user_manual.doctree differ diff --git a/docs/1.0/.nojekyll b/docs/1.0/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/1.0/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns b/docs/1.0/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns new file mode 100644 index 00000000..d6427376 Binary files /dev/null and b/docs/1.0/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns differ diff --git a/docs/1.0/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns b/docs/1.0/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns new file mode 100644 index 00000000..4662cd0e Binary files /dev/null and b/docs/1.0/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns differ diff --git a/doc/introduction/images/dist_part/data_dist.svg b/docs/1.0/_images/data_dist.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/data_dist.svg rename to docs/1.0/_images/data_dist.svg diff --git a/doc/introduction/images/dist_part/data_dist_gnum.svg b/docs/1.0/_images/data_dist_gnum.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/data_dist_gnum.svg rename to docs/1.0/_images/data_dist_gnum.svg diff --git a/doc/introduction/images/dist_part/data_full.svg b/docs/1.0/_images/data_full.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/data_full.svg rename to docs/1.0/_images/data_full.svg diff --git a/doc/introduction/images/dist_part/data_part.svg b/docs/1.0/_images/data_part.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/data_part.svg rename to docs/1.0/_images/data_part.svg diff --git a/doc/introduction/images/dist_part/data_part_gnum.svg b/docs/1.0/_images/data_part_gnum.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/data_part_gnum.svg rename to docs/1.0/_images/data_part_gnum.svg diff --git a/doc/introduction/images/dist_part/dist_mesh.svg b/docs/1.0/_images/dist_mesh.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/dist_mesh.svg rename to docs/1.0/_images/dist_mesh.svg diff --git a/doc/introduction/images/dist_part/dist_mesh_arrays.svg b/docs/1.0/_images/dist_mesh_arrays.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/dist_mesh_arrays.svg rename to docs/1.0/_images/dist_mesh_arrays.svg diff --git a/doc/introduction/images/dist_part/dist_part_LN_to_GN.svg b/docs/1.0/_images/dist_part_LN_to_GN.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/dist_part_LN_to_GN.svg rename to docs/1.0/_images/dist_part_LN_to_GN.svg diff --git a/doc/introduction/images/trees/dist_tree.png b/docs/1.0/_images/dist_tree.png similarity index 100% rename from doc/introduction/images/trees/dist_tree.png rename to docs/1.0/_images/dist_tree.png diff --git a/doc/introduction/images/trees/dist_tree_expl.png b/docs/1.0/_images/dist_tree_expl.png similarity index 100% rename from doc/introduction/images/trees/dist_tree_expl.png rename to docs/1.0/_images/dist_tree_expl.png diff --git a/doc/introduction/images/dist_part/full_mesh.svg b/docs/1.0/_images/full_mesh.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/full_mesh.svg rename to docs/1.0/_images/full_mesh.svg diff --git a/doc/introduction/images/dist_part/part_mesh.svg b/docs/1.0/_images/part_mesh.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/part_mesh.svg rename to docs/1.0/_images/part_mesh.svg diff --git a/doc/introduction/images/dist_part/part_mesh_arrays.svg b/docs/1.0/_images/part_mesh_arrays.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/introduction/images/dist_part/part_mesh_arrays.svg rename to docs/1.0/_images/part_mesh_arrays.svg diff --git a/doc/introduction/images/trees/part_tree.png b/docs/1.0/_images/part_tree.png similarity index 100% rename from doc/introduction/images/trees/part_tree.png rename to docs/1.0/_images/part_tree.png diff --git a/doc/introduction/images/trees/part_tree_expl.png b/docs/1.0/_images/part_tree_expl.png similarity index 100% rename from doc/introduction/images/trees/part_tree_expl.png rename to docs/1.0/_images/part_tree_expl.png diff --git a/doc/images/qs_basic.png b/docs/1.0/_images/qs_basic.png similarity index 100% rename from doc/images/qs_basic.png rename to docs/1.0/_images/qs_basic.png diff --git a/doc/images/qs_pycgns.png b/docs/1.0/_images/qs_pycgns.png old mode 100755 new mode 100644 similarity index 100% rename from doc/images/qs_pycgns.png rename to docs/1.0/_images/qs_pycgns.png diff --git a/doc/images/qs_workflow.png b/docs/1.0/_images/qs_workflow.png similarity index 100% rename from doc/images/qs_workflow.png rename to docs/1.0/_images/qs_workflow.png diff --git a/doc/introduction/images/trees/tree_seq.png b/docs/1.0/_images/tree_seq.png similarity index 100% rename from doc/introduction/images/trees/tree_seq.png rename to docs/1.0/_images/tree_seq.png diff --git a/doc/user_manual/images/workflow.svg b/docs/1.0/_images/workflow.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/user_manual/images/workflow.svg rename to docs/1.0/_images/workflow.svg diff --git a/doc/developer_manual/algo_description.rst b/docs/1.0/_sources/developer_manual/algo_description.rst.txt similarity index 100% rename from doc/developer_manual/algo_description.rst rename to docs/1.0/_sources/developer_manual/algo_description.rst.txt diff --git a/doc/developer_manual/algo_description/elements_to_ngons.rst b/docs/1.0/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt similarity index 100% rename from doc/developer_manual/algo_description/elements_to_ngons.rst rename to docs/1.0/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt diff --git a/doc/developer_manual/developer_manual.rst b/docs/1.0/_sources/developer_manual/developer_manual.rst.txt similarity index 100% rename from doc/developer_manual/developer_manual.rst rename to docs/1.0/_sources/developer_manual/developer_manual.rst.txt diff --git a/doc/developer_manual/logging.rst b/docs/1.0/_sources/developer_manual/logging.rst.txt similarity index 100% rename from doc/developer_manual/logging.rst rename to docs/1.0/_sources/developer_manual/logging.rst.txt diff --git a/doc/developer_manual/maia_dev/conventions.rst b/docs/1.0/_sources/developer_manual/maia_dev/conventions.rst.txt similarity index 100% rename from doc/developer_manual/maia_dev/conventions.rst rename to docs/1.0/_sources/developer_manual/maia_dev/conventions.rst.txt diff --git a/doc/developer_manual/maia_dev/development_workflow.rst b/docs/1.0/_sources/developer_manual/maia_dev/development_workflow.rst.txt similarity index 100% rename from doc/developer_manual/maia_dev/development_workflow.rst rename to docs/1.0/_sources/developer_manual/maia_dev/development_workflow.rst.txt diff --git a/docs/1.0/_sources/index.rst.txt b/docs/1.0/_sources/index.rst.txt new file mode 100644 index 00000000..ee2e8a36 --- /dev/null +++ b/docs/1.0/_sources/index.rst.txt @@ -0,0 +1,42 @@ +**************** +Welcome to Maia! +**************** + +**Maia** is a C++ and Python library for parallel algorithms over CGNS trees: distributed IO, partitioning, data management and various kind of transformations (generation of faces, destructuration, extractions, ...). + +Maia is an open source software developed at `ONERA `_. +Associated source repository and issue tracking are hosted on `Gitlab `_. + +!!!! + +Documentation summary +--------------------- + +:ref:`Quick start ` is the perfect page for a first overview of Maia or to retrieve the environments to use on Onera's clusters. + +:ref:`Introduction ` details the extensions made to the CGNS standard in order to define parallel CGNS trees. + +:ref:`User Manual ` is the main part of this documentation. It describes most of the high level APIs provided by Maia. + +:ref:`Developer Manual ` (under construction) provides more details on some algorithms and can be consulted if you want to contribute to Maia. + + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Reference + + quick_start + installation + introduction/introduction + user_manual/user_manual + developer_manual/developer_manual + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Appendix + + releases/release_notes + related_projects + license diff --git a/doc/installation.rst b/docs/1.0/_sources/installation.rst.txt similarity index 100% rename from doc/installation.rst rename to docs/1.0/_sources/installation.rst.txt diff --git a/docs/1.0/_sources/introduction/introduction.rst.txt b/docs/1.0/_sources/introduction/introduction.rst.txt new file mode 100644 index 00000000..29f36576 --- /dev/null +++ b/docs/1.0/_sources/introduction/introduction.rst.txt @@ -0,0 +1,302 @@ +.. _intro: + +Introduction +============ + +These section introduces the core concepts of distributed and partitioned data +used in Maia, and their application to define parallel CGNS trees. + +Core concepts +------------- + +Dividing data +^^^^^^^^^^^^^ + +:def:`Global data` is the complete data that describes an object. Let's represent it as the +following ordered shapes: + +.. image:: ./images/dist_part/data_full.svg + +Now imagine that you want to split this data into N pieces (N=3 for the next illustrations). +Among all the possibilities, we distinguishes two ways to do it: + +1. Preserving order: we call such repartition :def:`distributed data`, and we use the term :def:`block` + to refer to a piece of this distributed data. + + .. image:: ./images/dist_part/data_dist.svg + + Several distributions are possible, depending on where data is cut, but they all share the same properties: + + - the original order is preserved across the distributed data, + - each element appears in one and only one block, + - a block can be empty as long as the global order is preserved (b). + +2. Taking arbitrary subsets of the original data: we call such subsets :def:`partitioned data`, and we use the term :def:`partition` + to refer to a piece of this partitioned data. + + .. image:: ./images/dist_part/data_part.svg + + Due to this relaxed constraint, there is much more admissible splits since we allow in our definition the following cases: + + - an element can appear in several partitions, or several times within the same partition (b), + - it is allowed that an element does not appear in a partition (c). + + Such repartitions are often useful when trying to gather the elements depending on + some characteristics: on the above example, we created the partition of squared shaped elements, round shaped + elements and unfilled elements (b). Thus, some elements belong to more than one partition. + +A key point is that no *absolute* best way of diving data: depending of what we want to do with the +data, one of the way to divide it may be more adapted. In the previous example: + +- distributed data is fine if you want to count the number of filled shapes: you can count in each + block and then sum the result over the blocks. +- Now assume that you want to renumber the elements depending on their shape, then on their color: + if partitioned data (b) is used, partitions 1 and 2 could independently order + their elements by color since they are already sorted by shape [#f1]_. + +Numberings +^^^^^^^^^^ + +In order to describe the link between our divisions and the original global data, we need to +define additional concepts. + +For distributed data, since the original ordering is respected, the link with the global data is totally implicit: +we just need to know the number of elements in each block or, equivalently, the :def:`distribution array` +of the data. This is an array of size :mono:`N+1` indicating the bounds of each block. By convention, distribution +array starts at 0 and uses semi-open intervals. + + +.. image:: ./images/dist_part/data_dist_gnum.svg + +With this information, the global number of the jth element in the ith block is given by +:math:`\mathtt{dist[i] + j + 1}`. + +On the contrary, for partitioned data, we have to explicitly store the link with the global data: +we use a :def:`local to global numbering array` (often called :mono:`LN_to_GN` for short). +Each partition has its own :mono:`LN_to_GN` array whose size is the number of elements in the partition. + +.. image:: ./images/dist_part/data_part_gnum.svg + +Then, the global number of the jth element in the ith partition is simply given by +:math:`\mathtt{LN\_to\_GN[i][j]}`. + +For any global data, these additional informations allow to create a mapping beetween global data, partitioned +data and distributed data. Thus, it is always possible to reconstruct one of the views from another one. + +Application to MPI parallelism +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The concepts introduced above make all sense in the context of distributed memory computers. +In such architecture, the global data is in fact never detained by a single proc (it would be too heavy): +we always use a distributed or partitioned view of this data. +Using a good repartition is often the key of a well balanced algorithm. + +In the distributed view, we produce as much blocks as the number of MPI processes. Each process holds +its own block, and the distribution array, of size :mono:`n_rank+1`, is know by each process. + +In the partitioned view, we often produce one partition per process; but it can sometime be useful to +put several partitions on a given process. Each process holds the data associated to its partition, +including the related :mono:`LN\_to\_GN` arrays (:mono:`LN\_to\_GN` related to the other partitions +are not know by the current process). + +The :ref:`ParaDiGM ` library provides some low level methods to exchange data between the partitioned and +distributed views in a MPI context, with options to deal with redundant elements, create new distributions, +manage variable strides, etc. + + +Application to meshes +--------------------- + +Until now, we have seen the definition of distributed and partitioned data for a single array +of global data. Let see how to apply these concepts to a basic CFD mesh. + +Consider the following 12-vertices (green numbers), 6-quads (purple numbers) mesh and +assume that it is described by a cell to vertex connectivity. Thus, the mesh file defines +3 global arrays: + +- the CoordinateX and CoordinateY arrays, each one of size 12 +- the Connectivity array of size 6*4 = 24 + +.. image:: ./images/dist_part/full_mesh.svg + +If we have 2 processes at our disposal, a parallel way to load this mesh is to +distribute all the vertex-related entities with a **distribution array** of :code:`[0,6,12]` +and all the element-related entities with a distribution array of :code:`[0,3,6]` [#f2]_: + +.. image:: ./images/dist_part/dist_mesh_arrays.svg + +Then, the blue part of the arrays will be stored on the first process and the red part on the second process. +Looking back at the original mesh, this correspond to the following distributed meshes: + +.. image:: ./images/dist_part/dist_mesh.svg + +with the blue entities stored on the first process, and the red ones on the second process. + + +Notice that the distributed mesh is not suited for solver computation. For instance, cells on the domain treated by the first process are not connex. Worst yet, vertex and element quantities are distributed independently. Take a look at cell 1: it is blue so it belongs to process 0. However,its vertices 9 and 10 are on process 1, this means that there coordinates are not accessible directly on process 0. For these reasons, we want to properly partition our mesh, such as this: + +.. image:: ./images/dist_part/part_mesh.svg + +.. image:: ./images/dist_part/part_mesh_arrays.svg + +Now we have two semi-independent meshes and we can reason about each element with all its associated data +present on the same process. This partitioned view of the mesh has the two following properties: + + - Coherency: every data array is addressable locally, + - Connexity: the data represents geometrical entities that define a local subregion of the mesh. + +We want to keep the link between the base mesh and its partitioned version. For that, we need to store :def:`global numbering arrays`, quantity by quantity: + +.. image:: ./images/dist_part/dist_part_LN_to_GN.svg + +For example, for sub-mesh 0, we can check that element number 3 is actually element number 6 of the original mesh. +We can also see that vertex 3 of sub-mesh 0, and vertex 1 of submesh 1 are actually the same, that is, vertex 4 of the global mesh. + +Thanks to these global numbering, any quantity computed on the partitioned meshes (such as a vertex based field) +can then be transfered to the corresponding distributed vertex, before writting back the results. + +Maia CGNS trees +--------------- + +Overview +^^^^^^^^ + +Since Python/CGNS trees describe CFD meshes, we can apply the previous concepts to +define parallel trees. + +A :def:`full tree` is a tree as it is inside a CGNS file, or how it would be loaded by only one process. A full tree is **global data**. + +A :def:`dist tree` is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. See :ref:`dist_tree`. + +A :def:`part tree` is a partial tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. See :ref:`part_tree`. + +A :def:`size tree` is a tree in which only the size of the data is stored. A *size tree* is typically *global data* because each process needs it to know which *block* of data it will have to load and store. + +([Legacy] A :def:`skeleton tree` is a collective tree in which fields and element connectivities are not loaded) + +As explained in previous section, the standard full tree does not make sense in the context of a MPI parallel +application: all the trees encountered within the execution of maia are **distributed trees** or **partitioned trees**. +The next section describe the specification of these trees. + +Specification +^^^^^^^^^^^^^ + +Let us use the following tree as an example: + +.. image:: ./images/trees/tree_seq.png + +This tree is a **global tree**. It may appear like that on a HDF5/CGNS file, or if loaded entirely on one process as a Python/CGNS tree. + +.. _dist_tree: + +Distributed trees +""""""""""""""""" + +A :def:`dist tree` is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. + +If we distribute our tree over two processes, we would then have something like that: + +.. image:: ./images/trees/dist_tree.png + +Let us look at one of them and annotate nodes specific to the distributed tree: + +.. image:: ./images/trees/dist_tree_expl.png + +Arrays of non-constant size are distributed: fields, connectivities, :cgns:`PointLists`. +Others (:cgns:`PointRanges`, :cgns:`CGNSBase_t` and :cgns:`Zone_t` dimensions...) are of limited size and therefore replicated on all processes with virtually no memory penalty. + +On each process, for each entity kind, a **partial distribution** is stored, that gives information of which block of the arrays are stored locally. + +For example, for process 0, the distribution array of vertices of :cgns:`MyZone` is located at :cgns:`MyBase/MyZone/Distribution/Vertex` and is equal to :code:`[0, 9, 18]`. It means that only indices in the semi-open interval :code:`[0 9)` are stored by the **dist tree** on this process, and that the total size of the array is :code:`18`. +This partial distribution applies to arrays spaning all the vertices of the zone, e.g. :cgns:`CoordinateX`. + +More formally, a :def:`partial distribution` related to an entity kind :code:`E` is an array :code:`[start,end,total_size]` of 3 integers where :code:`[start:end)` is a closed/open interval giving, for all global arrays related to :code:`E`, the sub-array that is stored locally on the distributed tree, and :code:`total_size` is the global size of the arrays related to :code:`E`. + +The distributed entities are: + +.. glossary:: + Vertices and Cells + The **partial distribution** are stored in :cgns:`Distribution/Vertex` and :cgns:`Distribution/Cell` nodes at the level of the :cgns:`Zone_t` node. + + Used for example by :cgns:`GridCoordinates_t` and :cgns:`FlowSolution_t` nodes if they do not have a :cgns:`PointList` (i.e. if they span the entire vertices/cells of the zone) + + Quantities described by a :cgns:`PointList` or :cgns:`PointRange` + The **partial distribution** is stored in a :cgns:`Distribution/Index` node at the level of the :cgns:`PointList/PointRange` + + For example, :cgns:`ZoneSubRegion_t` and :cgns:`BCDataSet_t` nodes. + + If the quantity is described by a :cgns:`PointList`, then the :cgns:`PointList` itself is distributed the same way (in contrast, a :cgns:`PointRange` is fully replicated across processes because it is lightweight) + + Connectivities + The **partial distribution** is stored in a :cgns:`Distribution/Element` node at the level of the :cgns:`Element_t` node. Its values are related to the elements, not the vertices of the connectivity array. + + If the element type is heterogenous (NGon, NFace or MIXED) a :cgns:`Distribution/ElementConnectivity` is also present, and this partial distribution is related to the :cgns:`ElementConnectivity` array. + +.. note:: + A distributed tree object is not norm-compliant since most of its arrays are partial: on the previous example, + :cgns:`CoordinateX` array on rank 0 has a length of 9 when :cgns:`MyZone` declares 18 vertices. + However, the union of all the distributed tree objects represents a norm-compliant CGNS tree. + +.. _part_tree: + +Partitioned trees +""""""""""""""""" + +A :def:`part tree` is a partial CGNS tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. + +If we take the global tree from before and partition it, we may get the following tree: + +.. image:: ./images/trees/part_tree.png + +If we annotate the first one: + +.. image:: ./images/trees/part_tree_expl.png + +A **part tree** is just a regular, norm-compliant tree with additional information (in the form of :cgns:`GlobalNumbering` nodes) that keeps the link with the unpartitioned tree it comes from. Notice that the tree structure is **not** necessarily the same across all processes. + +The :cgns:`GlobalNumbering` nodes are located at the same positions that the :cgns:`Distribution` nodes were in the distributed tree. + +A :cgns:`GlobalNumbering` contains information to link an entity in the partition to its corresponding entity in the original tree. For example, the element section :cgns:`Hexa` has a global numbering array of value :code:`[3 4]`. It means: + +* Since it is an array of size 2, there is 2 elements in this section (which is confirmed by the :cgns:`ElementRange`) , +* The first element was the element of id :code:`3` in the original mesh, +* The second element was element :code:`4` in the original mesh. + +Naming conventions +"""""""""""""""""" + +When partitioning, some nodes are split, so there is convention to keep track of the fact they come from the same original node: + +* :cgns:`Zone_t` nodes : :cgns:`MyZone` is split in :cgns:`MyZone.PX.NY` where `X` is the rank of the process, and `Y` is the id of the zone on process `X`. +* Splitable nodes (notably :cgns:`GC_t`) : :cgns:`MyNode` is split in :cgns:`MyNode.N`. They appear in the following scenario: + + * We partition for 3 processes + * :cgns:`Zone0` is connected to :cgns:`Zone1` through :cgns:`GridConnectivity_0_to_1` + * :cgns:`Zone0` is not split (but goes to process 0 and becomes :cgns:`Zone0.P0.N0`). Zone1 is split into :cgns:`Zone1.P1.N0` and :cgns:`Zone1.P2.N0`. Then :cgns:`GridConnectivity_0_to_1` of :cgns:`Zone0` must be split into :cgns:`GridConnectivity_0_to_1.1` and :cgns:`GridConnectivity_0_to_1.2`. + +Note that partitioning may induce new :cgns:`GC_t` internal to the original zone being splitted. Their name is implementation-defined and those nodes do not have a :cgns:`GlobalNumbering` since they did not exist in the original mesh. + +.. _maia_tree: + +Maia trees +^^^^^^^^^^ + +A CGNS tree is said to be a :def:`Maia tree` if it has the following properties: + +* For each unstructured zone, the :cgns:`ElementRange` of all :cgns:`Elements_t` sections + + * are contiguous + * are ordered by ascending dimensions (i.e. edges come first, then faces, then cells) + * the first section starts at 1 + * there is at most one section by element type (e.g. not possible to have two :cgns:`QUAD_4` sections) + +Notice that this is property is required by **some** functions of Maia, not all of them! + +A **Maia tree** may be a **global tree**, a **distributed tree** or a **partitioned tree**. + +.. rubric:: Footnotes + +.. [#f1] Actually, such algorithm could be written for distributed data, but is less intuitive and require more knowledge of what + if happening on the other blocks. + +.. [#f2] Other distributions are possible : we could, for example, affect all the CoordinateX array on the first process (using the distribution array :code:`[0,12,12]`) and the CoordinateY array on the second, but we would have to manage a different distribution for each array. diff --git a/doc/license.rst b/docs/1.0/_sources/license.rst.txt similarity index 100% rename from doc/license.rst rename to docs/1.0/_sources/license.rst.txt diff --git a/docs/1.0/_sources/quick_start.rst.txt b/docs/1.0/_sources/quick_start.rst.txt new file mode 100644 index 00000000..f34517f8 --- /dev/null +++ b/docs/1.0/_sources/quick_start.rst.txt @@ -0,0 +1,169 @@ +.. _quick_start: + +.. currentmodule:: maia + +Quick start +=========== + +Environnements +-------------- + +We provide ready-to-go environments including Maia and its dependencies on the following clusters: + +**Spiro-EL8** + +This is the recommended environment for standalone usage of Maia. It works with intel mpi library (2021) +and python version 3.9. + +.. code-block:: sh + + module purge + source /scratchm/sonics/dist/spiro_el8.sh --compiler=gcc@12 --mpi=impi + + export MAIA_HOME=/scratchm/jcoulet/aa_install_py3/maia/opt-impi21 + export LD_LIBRARY_PATH=$MAIA_HOME/lib:$LD_LIBRARY_PATH + export PYTHONPATH=$MAIA_HOME/lib/python3.9/site-packages:$PYTHONPATH + +If you want to use maia in elsA framework, the next installation is compatible with +elsA spiro-el8_mpi production : + +.. code-block:: sh + + module purge + source /stck/elsa/Public/v5.1.03/Dist/bin/spiro-el8_mpi/source.me + + export MAIA_HOME=/scratchm/jcoulet/aa_install_py3/maia/opt-cfd5_21/ + export LD_LIBRARY_PATH=$MAIA_HOME/lib:$LD_LIBRARY_PATH + export PYTHONPATH=$MAIA_HOME/lib/python3.7/site-packages:$PYTHONPATH + +**Sator** + +Sator's version is compiled with intel mpi library and support of large integers. + +.. code-block:: sh + + module purge + source /tmp_user/sator/sonics/source-intel-oneapi-2021.2-sator-centos8.me --compiler gcc8.3 + + export MAIA_HOME=/tmp_user/sator/jcoulet/opt/maia + export LD_LIBRARY_PATH=$MAIA_HOME/lib:$LD_LIBRARY_PATH + export PYTHONPATH=$MAIA_HOME/lib/python3.8/site-packages:$PYTHONPATH + + +If you prefer to build your own version of Maia, see :ref:`installation` section. + +Supported meshes +---------------- + +Maia supports CGNS meshes from version 4.2, meaning that polyhedral connectivities (NGON_n, NFACE_n +and MIXED nodes) must have the ``ElementStartOffset`` node. + +Former meshes can be converted with the (sequential) maia_poly_old_to_new script included +in the ``$PATH`` once the environment is loaded: + +.. code-block:: sh + + $> maia_poly_old_to_new mesh_file.hdf + +The opposite maia_poly_new_to_old script can be used to put back meshes +in old conventions, insuring compatibility with legacy tools. + +.. warning:: CGNS databases should respect the `SIDS `_. + The most commonly observed non-compliant practices are: + + - Empty ``DataArray_t`` (of size 0) under ``FlowSolution_t`` containers. + - 2D shaped (N1,N2) ``DataArray_t`` under ``BCData_t`` containers. + These arrays should be flat (N1xN2,). + - Implicit ``BCDataSet_t`` location for structured meshes: if ``GridLocation_t`` + and ``PointRange_t`` of a given ``BCDataSet_t`` differs from the + parent ``BC_t`` node, theses nodes should be explicitly defined at ``BCDataSet_t`` + level. + + Several non-compliant practices can be detected with the ``cgnscheck`` utility. Do not hesitate + to check your file if Maia is unable to read it. + +Note also that ADF files are not supported; CGNS files should use the HDF binary format. ADF files can +be converted to HDF thanks to ``cgnsconvert``. + +Highlights +---------- + +.. tip:: Download sample files of this section: + :download:`S_twoblocks.cgns <../share/_generated/S_twoblocks.cgns>`, + :download:`U_ATB_45.cgns <../share/_generated/U_ATB_45.cgns>` + +.. rubric:: Daily user-friendly pre & post processing + +Maia provides simple Python APIs to easily setup pre or post processing operations: +for example, converting a structured tree into an unstructured (NGon) tree +is as simple as + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #basic_algo@start + :end-before: #basic_algo@end + :dedent: 2 + +.. image:: ./images/qs_basic.png + :width: 75% + :align: center + +In addition of being parallel, the algorithms are as much as possible topologic, meaning that +they do not rely on a geometric tolerance. This also allow us to preserve the boundary groups +included in the input mesh (colored on the above picture). + +.. rubric:: Building efficient workflows + +By chaining this elementary blocks, you can build a **fully parallel** advanced workflow +running as a **single job** and **minimizing file usage**. + +In the following example, we load an angular section of the +`ATB case `_, +duplicate it to a 180° case, split it, and perform some slices and extractions. + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #workflow@start + :end-before: #workflow@end + :dedent: 2 + +.. image:: ./images/qs_workflow.png + +The above illustration represents the input mesh (gray volumic block) and the +extracted surfacic tree (plane slice and extracted wall BC). Curved lines are +the outline of the volumic mesh after duplication. + +.. rubric:: Compliant with the pyCGNS world + +Finally, since Maia uses the standard `CGNS/Python mapping +`_, +you can set up applications involving multiple python packages: +here, we create and split a mesh with maia, but we then call Cassiopee functions +to compute the gradient of a field on each partition. + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #pycgns@start + :end-before: #pycgns@end + :dedent: 2 + +.. image:: ./images/qs_pycgns.png + +Be aware that other tools often expect to receive geometrically +consistent data, which is why we send the partitioned tree to +Cassiopee functions. The outline of this partitions (using 4 processes) +are materialized by the white lines on the above figure. + + +Resources and Troubleshouting +----------------------------- + +The :ref:`user manual ` describes most of the high level APIs provided by Maia. +If you want to be comfortable with the underlying concepts of distributed and +partitioned trees, have a look at the :ref:`introduction ` section. + +The user manual is illustrated with basic examples. Additional +test cases can be found in +`the sources `_. + +Issues can be reported on +`the gitlab board `_ +and help can also be asked on the dedicated +`Element room `_. diff --git a/doc/related_projects.rst b/docs/1.0/_sources/related_projects.rst.txt similarity index 100% rename from doc/related_projects.rst rename to docs/1.0/_sources/related_projects.rst.txt diff --git a/docs/1.0/_sources/releases/release_notes.rst.txt b/docs/1.0/_sources/releases/release_notes.rst.txt new file mode 100644 index 00000000..e70acc62 --- /dev/null +++ b/docs/1.0/_sources/releases/release_notes.rst.txt @@ -0,0 +1,12 @@ +.. _release_notes: + +Release notes +============= + +.. _whatsnew: + +.. currentmodule:: maia + +This page contains information about what has changed in each new version of **maia**. + +.. include:: v0.1.0.rst diff --git a/docs/1.0/_sources/releases/v0.1.0.rst.txt b/docs/1.0/_sources/releases/v0.1.0.rst.txt new file mode 100644 index 00000000..b369a4d6 --- /dev/null +++ b/docs/1.0/_sources/releases/v0.1.0.rst.txt @@ -0,0 +1,7 @@ +************************ +v0.1.0 (2021) +************************ +First documented release + +Features +"""""""" diff --git a/docs/1.0/_sources/user_manual/algo.rst.txt b/docs/1.0/_sources/user_manual/algo.rst.txt new file mode 100644 index 00000000..c352e70d --- /dev/null +++ b/docs/1.0/_sources/user_manual/algo.rst.txt @@ -0,0 +1,104 @@ +Algo module +=========== + +The ``maia.algo`` module provides various algorithms to be applied to one of the +two kind of trees defined by Maia: + +- ``maia.algo.dist`` module contains some operations applying on distributed trees +- ``maia.algo.part`` module contains some operations applying on partitioned trees + +In addition, some algorithms can be applied indistinctly to distributed or partitioned trees. +These algorithms are accessible through the ``maia.algo`` module. + +The ``maia.algo.seq`` module contains a few sequential utility algorithms. + +.. _user_man_dist_algo: + +Distributed algorithms +---------------------- + +The following algorithms applies on maia distributed trees. + + +Connectivities conversions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.convert_s_to_u +.. autofunction:: maia.algo.dist.convert_elements_to_ngon +.. autofunction:: maia.algo.dist.ngons_to_elements +.. autofunction:: maia.algo.dist.convert_elements_to_mixed +.. autofunction:: maia.algo.dist.convert_mixed_to_elements +.. autofunction:: maia.algo.dist.rearrange_element_sections +.. autofunction:: maia.algo.dist.generate_jns_vertex_list + + +Geometry transformations +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.duplicate_from_rotation_jns_to_360 +.. autofunction:: maia.algo.dist.merge_zones +.. autofunction:: maia.algo.dist.merge_zones_from_family +.. autofunction:: maia.algo.dist.merge_connected_zones +.. autofunction:: maia.algo.dist.conformize_jn_pair + +Data management +^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.redistribute_tree + +.. + from .extract_surf_dmesh import extract_surf_tree_from_bc + +.. _user_man_part_algo: + +Partitioned algorithms +---------------------- + +The following algorithms applies on maia partitioned trees. + +Geometric calculations +^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.compute_cell_center +.. autofunction:: maia.algo.part.compute_face_center +.. autofunction:: maia.algo.part.compute_edge_center +.. autofunction:: maia.algo.part.compute_wall_distance +.. autofunction:: maia.algo.part.localize_points +.. autofunction:: maia.algo.part.find_closest_points + +Mesh extractions +^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.iso_surface +.. autofunction:: maia.algo.part.plane_slice +.. autofunction:: maia.algo.part.spherical_slice +.. autofunction:: maia.algo.part.extract_part_from_zsr +.. autofunction:: maia.algo.part.extract_part_from_bc_name + +Interpolations +^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.interpolate_from_part_trees +.. autofunction:: maia.algo.part.centers_to_nodes + +.. _user_man_gen_algo: + + +Generic algorithms +------------------ + +The following algorithms applies on maia distributed or partitioned trees + +.. autofunction:: maia.algo.transform_affine +.. autofunction:: maia.algo.pe_to_nface +.. autofunction:: maia.algo.nface_to_pe + + +Sequential algorithms +--------------------- + +The following algorithms applies on regular pytrees. + +.. autofunction:: maia.algo.seq.poly_new_to_old +.. autofunction:: maia.algo.seq.poly_old_to_new +.. autofunction:: maia.algo.seq.enforce_ngon_pe_local diff --git a/doc/user_manual/config.rst b/docs/1.0/_sources/user_manual/config.rst.txt similarity index 100% rename from doc/user_manual/config.rst rename to docs/1.0/_sources/user_manual/config.rst.txt diff --git a/docs/1.0/_sources/user_manual/factory.rst.txt b/docs/1.0/_sources/user_manual/factory.rst.txt new file mode 100644 index 00000000..eea5737d --- /dev/null +++ b/docs/1.0/_sources/user_manual/factory.rst.txt @@ -0,0 +1,112 @@ +Factory module +============== + +The ``maia.factory`` module can be used to generate parallel CGNS trees. Those can +be obtained from an other kind of tree, or can be generated from user parameters. + +Generation +---------- + +.. autofunction:: maia.factory.generate_dist_block +.. autofunction:: maia.factory.distribute_tree + +Partitioning +------------ + +.. autofunction:: maia.factory.partition_dist_tree + +Partitioning options +^^^^^^^^^^^^^^^^^^^^ +Partitioning can be customized with the following keywords arguments: + +.. py:attribute:: graph_part_tool + + Graph partitioning library to use to split unstructured blocks. Irrelevent for structured blocks. + + :Admissible values: ``parmetis``, ``ptscotch``, ``hilbert``. Blocks defined by nodal elements does + not support hilbert method. + :Default value: ``parmetis``, if installed; else ``ptscotch``, if installed; ``hilbert`` otherwise. + +.. py:attribute:: zone_to_parts + + Control the number, size and repartition of partitions. See :ref:`user_man_part_repartition`. + + :Default value: Computed such that partitioning is balanced using + :func:`maia.factory.partitioning.compute_balanced_weights`. + +.. py:attribute:: part_interface_loc + + :cgns:`GridLocation` for the created partitions interfaces. Pre-existing interface keep their original location. + + :Admissible values: ``FaceCenter``, ``Vertex`` + :Default value: ``FaceCenter`` for unstructured zones with NGon connectivities; ``Vertex`` otherwise. + +.. py:attribute:: reordering + + Dictionnary of reordering options, which are used to renumber the entities in each partitioned zone. See + corresponding documentation. + +.. py:attribute:: preserve_orientation + + If True, the created interface faces are not reversed and keep their original orientation. Consequently, + NGonElements can have a zero left parent and a non zero right parent. + Only relevant for U/NGon partitions. + + :Default value: ``False`` + +.. py:attribute:: dump_pdm_output + + If True, dump the raw arrays created by paradigm in a :cgns:`CGNSNode` at (partitioned) zone level. For debug only. + + :Default value: ``False`` + +.. _user_man_part_repartition: + +Repartition +^^^^^^^^^^^ + +The number, size, and repartition (over the processes) of the created partitions is +controlled through the ``zone_to_parts`` keyword argument: each process must provide a +dictionary associating the path of every distributed zone to a list of floats. +For a given distributed zone, current process will receive as many partitions +as the length of the list (missing path or empty list means no partitions); the size of these partitions +corresponds to the values in the list (expressed in fraction of the input zone). +For a given distributed zone, the sum of all the fractions across all the processes must +be 1. + +This dictionary can be created by hand; for convenience, Maia provides three functions in the +:mod:`maia.factory.partitioning` module to create this dictionary. + +.. autofunction:: maia.factory.partitioning.compute_regular_weights +.. autofunction:: maia.factory.partitioning.compute_balanced_weights +.. autofunction:: maia.factory.partitioning.compute_nosplit_weights + +Reordering options +^^^^^^^^^^^^^^^^^^ + +For unstructured zones, the reordering options are transmitted to ParaDiGM in order to +control the renumbering of mesh entities in the partitions. + ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| Kwarg | Admissible values | Effect | Default | ++====================+===================================+=====================================+============================+ +| cell_renum_method | "NONE", "RANDOM", "HILBERT", | Renumbering method for the cells | NONE | +| | "CUTHILL", "CACHEBLOCKING", | | | +| | "CACHEBLOCKING2", "HPC" | | | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| face_renum_method | "NONE", "RANDOM", "LEXICOGRAPHIC" | Renumbering method for the faces | NONE | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| vtx_renum_method | "NONE", "SORT_INT_EXT" | Renumbering method for the vertices | NONE | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| n_cell_per_cache | Integer >= 0 | Specific to cacheblocking | 0 | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| n_face_per_pack | Integer >= 0 | Specific to cacheblocking | 0 | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| graph_part_tool | "parmetis", "ptscotch", | Graph partitioning library to | Same as partitioning tool | +| | "hyperplane" | use for renumbering | | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ + +Recovering from partitions +-------------------------- + +.. autofunction:: maia.factory.recover_dist_tree diff --git a/docs/1.0/_sources/user_manual/io.rst.txt b/docs/1.0/_sources/user_manual/io.rst.txt new file mode 100644 index 00000000..a0ad5f69 --- /dev/null +++ b/docs/1.0/_sources/user_manual/io.rst.txt @@ -0,0 +1,79 @@ +File management +=============== + +Maia supports HDF5/CGNS file reading and writing, +see `related documention `_. + +The IO functions are provided by the ``maia.io`` module. All the high level functions +accepts a ``legacy`` parameter used to control the low level CGNS-to-hdf driver: + +- if ``legacy==False`` (default), hdf calls are performed by the python module + `h5py `_. +- if ``legacy==True``, hdf calls are performed by + `Cassiopee.Converter `_ module. + +The requested driver should be installed on your computer as well as the +hdf5 library compiled with parallel support. + +.. _user_man_dist_io: + +Distributed IO +-------------- + +Distributed IO is the privileged way to deal with CGNS files within your maia workflows. +Files are loaded as distributed trees, and, inversely, distributed trees can be written +into a single CGNS file. + +High level IO operations can be performed with the two following functions, which read +or write all data they found : + +.. autofunction:: maia.io.file_to_dist_tree +.. autofunction:: maia.io.dist_tree_to_file + + +The example below shows how to uses these high level functions: + +.. literalinclude:: snippets/test_io.py + :start-after: #file_to_dist_tree_full@start + :end-before: #file_to_dist_tree_full@end + :dedent: 2 + +Finer control of what is written or loaded can be achieved with the following steps: + +- For a **write** operation, the easiest way to write only some nodes in + the file is to remove the unwanted nodes from the distributed tree. +- For a **read** operation, the load has to be divided into the following steps: + + - Loading a size_tree: this tree has only the shape of the distributed data and + not the data itself. + - Removing unwanted nodes in the size tree; + - Fill the filtered tree from the file. + +The example below illustrate how to filter the written or loaded nodes: + +.. literalinclude:: snippets/test_io.py + :start-after: #file_to_dist_tree_filter@start + :end-before: #file_to_dist_tree_filter@end + :dedent: 2 + +Writing partitioned trees +-------------------------- + +In some cases, it may be useful to write a partitioned tree (keeping the +partitioned zones separated). This can be achieved using the following function: + +.. autofunction:: maia.io.part_tree_to_file + +.. _user_man_raw_io: + +Raw IO +------ + +For debug purpose, trees can also be read or written independently in a +sequential manner. Be aware that information added by maia such as Distribution +or GlobalNumbering nodes will not be removed. + +.. autofunction:: maia.io.read_tree +.. autofunction:: maia.io.write_tree +.. autofunction:: maia.io.write_trees + diff --git a/doc/user_manual/transfer.rst b/docs/1.0/_sources/user_manual/transfer.rst.txt similarity index 100% rename from doc/user_manual/transfer.rst rename to docs/1.0/_sources/user_manual/transfer.rst.txt diff --git a/doc/user_manual/user_manual.rst b/docs/1.0/_sources/user_manual/user_manual.rst.txt similarity index 100% rename from doc/user_manual/user_manual.rst rename to docs/1.0/_sources/user_manual/user_manual.rst.txt diff --git a/docs/1.0/_static/basic.css b/docs/1.0/_static/basic.css new file mode 100644 index 00000000..bf18350b --- /dev/null +++ b/docs/1.0/_static/basic.css @@ -0,0 +1,906 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/1.0/_static/css/badge_only.css b/docs/1.0/_static/css/badge_only.css new file mode 100644 index 00000000..e380325b --- /dev/null +++ b/docs/1.0/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/1.0/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/1.0/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/1.0/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/1.0/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/1.0/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/1.0/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/1.0/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/1.0/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/1.0/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/1.0/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/1.0/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/1.0/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/1.0/_static/css/fonts/fontawesome-webfont.eot b/docs/1.0/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/docs/1.0/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/1.0/_static/css/fonts/fontawesome-webfont.svg b/docs/1.0/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/docs/1.0/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/1.0/_static/css/fonts/fontawesome-webfont.ttf b/docs/1.0/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/docs/1.0/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/1.0/_static/css/fonts/fontawesome-webfont.woff b/docs/1.0/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/docs/1.0/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/1.0/_static/css/fonts/fontawesome-webfont.woff2 b/docs/1.0/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/docs/1.0/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/1.0/_static/css/fonts/lato-bold-italic.woff b/docs/1.0/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/1.0/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/1.0/_static/css/fonts/lato-bold-italic.woff2 b/docs/1.0/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/1.0/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/1.0/_static/css/fonts/lato-bold.woff b/docs/1.0/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/1.0/_static/css/fonts/lato-bold.woff differ diff --git a/docs/1.0/_static/css/fonts/lato-bold.woff2 b/docs/1.0/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/1.0/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/1.0/_static/css/fonts/lato-normal-italic.woff b/docs/1.0/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/1.0/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/1.0/_static/css/fonts/lato-normal-italic.woff2 b/docs/1.0/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/1.0/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/1.0/_static/css/fonts/lato-normal.woff b/docs/1.0/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/1.0/_static/css/fonts/lato-normal.woff differ diff --git a/docs/1.0/_static/css/fonts/lato-normal.woff2 b/docs/1.0/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/1.0/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/1.0/_static/css/read_the_docs_custom.css b/docs/1.0/_static/css/read_the_docs_custom.css new file mode 100644 index 00000000..f164cfdf --- /dev/null +++ b/docs/1.0/_static/css/read_the_docs_custom.css @@ -0,0 +1,30 @@ +@import 'theme.css'; /* for the Read the Docs theme */ + +.rst-content div[class^="highlight"] pre { + font-size: 95%; +} + +/* +div.indexpage p { + font-size: 5.0em; +} +*/ + +/* override table no-wrap */ +.wy-table-responsive table td, .wy-table-responsive table th { + white-space: normal; +} + +.def { + color: #e41a1c; + font-weight: bold; +} + +.mono { + font-family: 'Courier New', monospace; +} + +.cgns { + color: #08306b; + font-family: 'Courier New', monospace; +} diff --git a/docs/1.0/_static/css/theme.css b/docs/1.0/_static/css/theme.css new file mode 100644 index 00000000..8cd4f101 --- /dev/null +++ b/docs/1.0/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li span.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li span.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li span.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li span.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li span.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p.caption .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.btn .wy-menu-vertical li span.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p.caption .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.nav .wy-menu-vertical li span.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p.caption .btn .headerlink,.rst-content p.caption .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li span.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol li,.rst-content ol.arabic li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content ol.arabic li p:last-child,.rst-content ol.arabic li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover span.toctree-expand,.wy-menu-vertical li.on a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand{display:block;font-size:.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp{user-select:none;pointer-events:none}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink{visibility:hidden;font-size:14px}.rst-content .code-block-caption .headerlink:after,.rst-content .toctree-wrapper>p.caption .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content p.caption .headerlink:after,.rst-content table>caption .headerlink:after{content:"\f0c1";font-family:FontAwesome}.rst-content .code-block-caption:hover .headerlink:after,.rst-content .toctree-wrapper>p.caption:hover .headerlink:after,.rst-content dl dt:hover .headerlink:after,.rst-content h1:hover .headerlink:after,.rst-content h2:hover .headerlink:after,.rst-content h3:hover .headerlink:after,.rst-content h4:hover .headerlink:after,.rst-content h5:hover .headerlink:after,.rst-content h6:hover .headerlink:after,.rst-content p.caption:hover .headerlink:after,.rst-content table>caption:hover .headerlink:after{visibility:visible}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl dt span.classifier:before{content:" : "}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code,html.writer-html4 .rst-content dl:not(.docutils) tt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/1.0/_static/doctools.js b/docs/1.0/_static/doctools.js new file mode 100644 index 00000000..e509e483 --- /dev/null +++ b/docs/1.0/_static/doctools.js @@ -0,0 +1,326 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + var url = new URL(window.location); + url.searchParams.delete('highlight'); + window.history.replaceState({}, '', url); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + break; + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + break; + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/1.0/_static/documentation_options.js b/docs/1.0/_static/documentation_options.js new file mode 100644 index 00000000..2fa8c97f --- /dev/null +++ b/docs/1.0/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/1.0/_static/file.png b/docs/1.0/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/docs/1.0/_static/file.png differ diff --git a/docs/1.0/_static/fonts/Lato/lato-bold.eot b/docs/1.0/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 00000000..3361183a Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/1.0/_static/fonts/Lato/lato-bold.ttf b/docs/1.0/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 00000000..29f691d5 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/1.0/_static/fonts/Lato/lato-bold.woff b/docs/1.0/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/1.0/_static/fonts/Lato/lato-bold.woff2 b/docs/1.0/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/1.0/_static/fonts/Lato/lato-bolditalic.eot b/docs/1.0/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 00000000..3d415493 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/1.0/_static/fonts/Lato/lato-bolditalic.ttf b/docs/1.0/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 00000000..f402040b Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/1.0/_static/fonts/Lato/lato-bolditalic.woff b/docs/1.0/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/1.0/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/1.0/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/1.0/_static/fonts/Lato/lato-italic.eot b/docs/1.0/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 00000000..3f826421 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/1.0/_static/fonts/Lato/lato-italic.ttf b/docs/1.0/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 00000000..b4bfc9b2 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/1.0/_static/fonts/Lato/lato-italic.woff b/docs/1.0/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/1.0/_static/fonts/Lato/lato-italic.woff2 b/docs/1.0/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/1.0/_static/fonts/Lato/lato-regular.eot b/docs/1.0/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 00000000..11e3f2a5 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/1.0/_static/fonts/Lato/lato-regular.ttf b/docs/1.0/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 00000000..74decd9e Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/1.0/_static/fonts/Lato/lato-regular.woff b/docs/1.0/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/1.0/_static/fonts/Lato/lato-regular.woff2 b/docs/1.0/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/1.0/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 00000000..79dc8efe Binary files /dev/null and b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 00000000..df5d1df2 Binary files /dev/null and b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 00000000..2f7ca78a Binary files /dev/null and b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 00000000..eb52a790 Binary files /dev/null and b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/1.0/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/1.0/_static/graphviz.css b/docs/1.0/_static/graphviz.css new file mode 100644 index 00000000..19e7afd3 --- /dev/null +++ b/docs/1.0/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/docs/1.0/_static/jquery-3.5.1.js b/docs/1.0/_static/jquery-3.5.1.js new file mode 100644 index 00000000..50937333 --- /dev/null +++ b/docs/1.0/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Algorithms description

+

This section provides a detailed description of some algorithms.

+ +
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/developer_manual/algo_description/elements_to_ngons.html b/docs/1.0/developer_manual/algo_description/elements_to_ngons.html new file mode 100644 index 00000000..24515716 --- /dev/null +++ b/docs/1.0/developer_manual/algo_description/elements_to_ngons.html @@ -0,0 +1,506 @@ + + + + + + + + + + elements_to_ngons — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

elements_to_ngons

+
+

Description

+
maia.algo.dist.elements_to_ngons(dist_tree_elts, comm)
+
+
+

Take a distributed CGNSTree_t or CGNSBase_t, and transform it into a distributed NGon/NFace mesh. The tree is modified in-place.

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD, stable_sort=True)
+
+
+
+
+

Arguments

+
+
dist_tree_elts
+
a distributed tree with
    +
  • unstructured zones that contain regular element sections (i.e. not NGON, not NFACE and not MIXED)

  • +
  • the Maia tree property

  • +
+
+
+
+
comm

a communicator over which elements_to_ngons is called

+
+
+
+
+

Output

+

The tree is modified in-place. The regular element sections are replaced by:

+
    +
  • a NGon section with:

    +
      +
    • An ElementStartOffset/ElementConnectivity node describing:

      +
        +
      • first the external faces in exactly the same order as they were (in particular, gathered by face type)

      • +
      • then the internal faces, also gathered by face type

      • +
      +
    • +
    • a ParentElements node and a ParentElementsPosition node

    • +
    +
  • +
  • a NFace section with cells ordered as they were, with ids only shifted by the number of interior faces

  • +
+
+
+

Parallelism

+

elements_to_ngons is a collective function.

+
+
+

Complexity

+

With \(N\) the number of zones, \(n_i\) the number of elements of zone \(i\), \(n_{f,i}\) its number of interior faces, and \(K\) the number of processes

+
+
Sequential time

\(\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i \cdot log(n_i) \right)\)

+

The cost is dominated by the sorting of faces (interior and exterior) used to spot identical ones.

+
+
Parallel time

\(\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i/K \cdot log(n_i) \right)\)

+

The parallel distributed sort algorithm consists of three steps:

+
+
    +
  1. A partitioning step that locally gather faces into \(K\) buckets that are of equal global size. This uses a \(K\)-generalized variant of quickselect that is of complexity \(\displaystyle \mathcal{O} \left( n_i/K \cdot log(K) \right)\)

  2. +
  3. An all_to_all exchange step that gather the \(K\) buckets on the \(K\) processes. This step is not accounted for here (see below)

  4. +
  5. A local sorting step that is \(\displaystyle \mathcal{O} \left( n_i/K \cdot log(n_i/K) \right)\)

  6. +
+
+

If we sum up step 1 and 3, we get

+
+
+
+\[\begin{split}\begin{equation} \label{eq1} +\begin{split} + \textrm{C}_\textrm{parallel_sort} & = \mathcal{O} \left( n_i/K \cdot log(K) + n_i/K \cdot log(n_i/K) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot \left( log(K) + log(n_i/K) \right) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot log(n_i) \right) +\end{split} +\end{equation}\end{split}\]
+
+
Theoretical scaling

\(\textrm{Speedup} = K\)

+

Experimentally, the scaling is much worse - under investigation.

+

Note: the speedup is computed by \(\textrm{Speedup} = t_s / t_p\) where \(t_s\) is the sequential time and \(t_p\) the parallel time. A speedup of \(K\) is perfect, a speedup lower than \(1\) means that sequential execution is faster.

+
+
Peak memory

Approximately \(\displaystyle \sum_{i=0}^N 2 n_i + n_{f,i}\)

+

This is the size of the input tree + the output tree. \(n_i\) is counted twice: once for the input element connectivity, once for the output NFace connectivity

+
+
Size of communications

Approximately \(\displaystyle \sum_{i=0}^N 3 n_{f,i} + n_i\) all_to_all calls

+

For each zone, one all_to_all call to sort interior faces, one to send back the faces to the NFace, one to concatenate all faces, one to concatenate all cells

+
+
Number of communication calls

Should be \(\displaystyle \mathcal{O} \left( \sum_{i=0}^N log(n_i/K) \right)\)

+

The number of communications is constant, except for the algorithm finding a balanced distribution of interior faces

+
+
Note

In practice, \(n_{f,i}\) varies from \(2 n_i\) (tet-dominant meshes) to \(3 n_i\) (hex-dominant meshes).

+
+
+
+
+

Algorithm explanation

+
maia::elements_to_ngons(cgns::tree& dist_zone, MPI_Comm comm)
+
+
+

The algorithm is divided in two steps:

+
    +
  1. Generate interior faces, insert them between exterior faces and cells. During the process, also compute the parent information (ParentElements and ParentElementsPosition) and add the CellFace connectivity to cells. All elements keep their standard element types, in particular interior faces are inserted by section type (e.g. there will be a TRI_3_interior node and a QUAD_4_interior node, not a NGon node)

  2. +
  3. Transform all sections into NGon/NFace

  4. +
+
+

Generate interior faces

+
+

Simplified algorithm

+

Let us look at a simplified sequential algorithm first:

+
1. For all element sections (3D and 2D):
+  Generate faces
+    => FaceVtx arrays (one for Tris, one for Quads)
+    => Associated Parent and ParentPosition arrays
+        (only one parent by element at this stage)
+  Interior faces are generated twice (by their two parent cells)
+  Exterior faces are generated twice (by a parent cell and a boundary face)
+
+2. For each element kind in {Tri,Quad}
+  Sort the FaceVtx array
+    (ordering: lexicographical comparison of vertices)
+  Sort the parent arrays accordingly
+    => now each face appears consecutively exactly twice
+        for interior faces,
+          the FaceVtx is always inverted between the two occurences
+        for exterior faces,
+          it depends on the normal convention
+
+Note that interior and exterior faces can be distinguised
+  by looking at the id of their parents
+
+3. For each interior face appearing twice:
+  Create an interior face section where:
+    the first FaceVtx is kept
+    the two parents are stored
+4. For each exterior face appearing twice:
+  One of the face is the original boundary section face,
+    one was generated from the joint cell
+  Send back to the original boundary face its parent face id and position
+    => store the parent information of the boundary face
+
+
+
+
+

Parallel algorithm

+

The algorithm is very similar to the sequential one. We need to modify two operations:

+
+
Sorting of the FaceVtx array (step 2)

The parallel sorting is done in three steps:

+
    +
  1. apply a partial sort std_e::sort_by_rank that will determine the rank of each FaceVtx

  2. +
  3. call an all_to_all communication step that sends each connectivity to its rank, based on the information of the previous step

  4. +
  5. sort each received FaceVtx locally

  6. +
+
+
Send back boundary parents and position to the original boundary faces (step 4)

Since the original face can be remote, this is a parallel communication operation using std_e::scatter

+
+
+
+
+

Computation of the CellFace

+

After step 2, we have all the faces exactly once, with their parent ids and parent positions. +We can then compute the CellFace of each cell section by the following algorithm:

+
For each cell section:
+  pre-allocate the CellFace array
+    (its size is n_cell_in_section * n_face_of_cell_type)
+  view it as a global distributed array
+For each unique face:
+  For each of its parent cells (could be one or two):
+    send the parent cell the id of the face and its position
+    insert the result in the CellFace array
+
+
+

As previously, the send operation uses a scatter pattern

+
+
+
+

Transform all sections into NGon/NFace

+

Thanks to the previous algorithm, we have:

+
    +
  • all exterior and interior faces with their parent information

  • +
  • the CellFace connectivity of the cell sections

  • +
+

Elements are ordered in something akin to this:

+
    +
  • boundary tris

  • +
  • boundary quads

  • +
  • internal tris

  • +
  • internal quads

  • +
  • tetras

  • +
  • pyras

  • +
  • prisms

  • +
  • hexas

  • +
+

The algorithm then just needs to:

+
    +
  • concatenate all FaceVtx of the faces into a NGon node and add a ElementStartOffset

  • +
  • concatenate all CellFace of the cells into a NFace node and add a ElementStartOffset

  • +
+
+
+
+

Design alternatives

+

The final step of the computation involves concatenating faces and cells global section by global section. This requires two heavyweight all_to_all calls. An alternative would be to concatenate locally. This would imply two trade-offs:

+
    +
  • the faces and cells would then not be globally gathered by type, and the exterior faces would not be first

  • +
  • all the PointLists (including those where GridLocation=FaceCenter) would have to be shifted

  • +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/developer_manual/developer_manual.html b/docs/1.0/developer_manual/developer_manual.html new file mode 100644 index 00000000..f5f3903b --- /dev/null +++ b/docs/1.0/developer_manual/developer_manual.html @@ -0,0 +1,274 @@ + + + + + + + + + + Developer Manual — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/developer_manual/logging.html b/docs/1.0/developer_manual/logging.html new file mode 100644 index 00000000..8e56bd3f --- /dev/null +++ b/docs/1.0/developer_manual/logging.html @@ -0,0 +1,378 @@ + + + + + + + + + + Log management — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Log management

+
+

Loggers

+

A logger is a global object where an application or a library can log to. +It can be declared with

+
from maia.utils.logging import add_logger
+
+add_logger("my_logger")
+
+
+

or with the equivalent C++ code

+
#include "std_e/logging/log.hpp"
+
+std_e::add_logger("my_logger");
+
+
+

A logger declared in Python is available in C++ and vice-versa: we do not need to create it twice.

+
+

Note

+

Loggers are available to pure C/C++ programs (in particular, test programs that do not use the Python interpreter).

+
+

It can then be referred to by its name. If we want to log a string to my_logger, we will do it like so:

+
from maia.utils.logging import log
+
+log('my_logger', 'my message')
+
+
+
#include "std_e/logging/log.hpp"
+
+std_e::log("my_logger", "my message");
+
+
+

Typically, an application will use several loggers to log messages by themes. By convention, the name of a logger in the application my_app should begin with my_app. For instance, loggers can be named: my_app, my_app-stats, my_app-errors

+

Loggers are both

+
    +
  • developper-oriented, that is, when a developper wants to output a message, he should select a logger (existing or not) encompassing the theme of the message he wants to log,

  • +
  • user-oriented, because a user can choose what logger he wants to listen to.

  • +
+
+
+

Printers

+

By itself, a logger does not do anything with the messages it receives. For that, we need to attach printers to a logger that will handle its messages.

+

For instance, we can attach a printer that will output the message to the console:

+
from maia.utils.logging import add_printer_to_logger
+add_printer_to_logger('my_logger','stdout_printer')
+
+
+

Printers are user-oriented: it is the user of the application who decides what he wants to do with the message that each logger is receiving, by attaching none or several printers to a logger.

+
+

Available printers

+
+
stdout_printer, stderr_printer

output messages to the console (respectively stdout and stderr)

+
+
mpi_stdout_printer, mpi_stderr_printer

output messages to the console, but prefix them by MPI.COMM_WORLD.Get_rank()

+
+
mpi_rank_0_stdout_printer, mpi_rank_0_stderr_printer

output messages to the console, if MPI.COMM_WORLD.Get_rank()==0

+
+
file_printer(‘my_file.extension’)

output messages to file my_file.extension

+
+
mpi_file_printer(‘my_file.extension’)

output messages to files my_file.{rk}.extension, with rk = MPI.COMM_WORLD.Get_rank()

+
+
+
+

Note

+

MPI-aware printers use MPI.COMM_WORLD rather than a local communicator. While the latter would be more correct, it would imply local instanciation of printers, at the time or after a new communicator is created. While this is certainly doable, up until now we feel that it does not worth the burden. But if the need arises, they can still be added.

+
+
+
+

Create your own printer

+

Any Python type can be used as a printer as long as it provides a log method that accepts a string argument.

+
from maia.utils.logging import add_printer_to_logger
+
+class my_printer:
+  def log(self, msg):
+    print(msg)
+
+add_printer_to_logger('my_logger',my_printer())
+
+
+
+
+
+

Configuration file

+

Loggers are associated to default printers. While they can be configured anytime in the Python scripts, most of the time, reading a configuration file at the start of the program is enough. The program will try to read a configuration file if the environment variable LOGGING_CONF_FILE is set. A logging configuration file looks like this:

+
my_app : mpi_stdout_printer
+my_app-my_theme : mpi_file_printer('my_theme.log')
+
+
+

For developpers, a logging file logging.conf with loggers and default printers is put in the build/ folder, and LOGGING_CONF_FILE is set accordingly.

+
+
+

Maia specifics

+

Maia provides 4 convenience functions that use Maia loggers

+
from maia.utils import logging as mlog
+mlog.info('info msg') # uses the 'maia' logger
+mlog.stat('stat msg') # uses the 'maia-stats' logger
+mlog.warning('warn msg') # uses the 'maia-warnings' logger
+mlog.error('error msg') # uses the 'maia-errors' logger
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/developer_manual/maia_dev/conventions.html b/docs/1.0/developer_manual/maia_dev/conventions.html new file mode 100644 index 00000000..0db74a85 --- /dev/null +++ b/docs/1.0/developer_manual/maia_dev/conventions.html @@ -0,0 +1,301 @@ + + + + + + + + + + Conventions — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Conventions

+
+

Naming conventions

+
    +
  • snake_case

  • +
  • A variable holding a number of things is written n_thing. Example: n_proc, n_vtx. The suffix is singular.

  • +
  • For unit tests, when testing variable <var>, the hard-coded expected variable is named expected_<var>.

  • +
  • Usual abbreviations

    +
      +
    • elt for element

    • +
    • vtx for vertex

    • +
    • proc for process

    • +
    • sz for size (only for local variable names, not functions)

    • +
    +
  • +
  • Connectivities

    +
      +
    • cell_vtx means mesh array of cells described by their vertices (CGNS example: HEXA_8)

    • +
    • cell_face means the cells described by their faces (CGNS example: NFACE_n)

    • +
    • face_cell means for each face, the two parent cells (CGNS example: ParentElement)

    • +
    • … so on: face_vtx, edge_vtx

    • +
    • elt_vtx, elt_face…: in this case, elt can be either a cell, a face or an edge

    • +
    +
  • +
+
+
+

Other conventions

+

We try to use semi-open intervals and 0-indexed structures. This is coherent with Python, C++, and their libraries, but unfortunately not with CGNS.

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/developer_manual/maia_dev/development_workflow.html b/docs/1.0/developer_manual/maia_dev/development_workflow.html new file mode 100644 index 00000000..1acbec8a --- /dev/null +++ b/docs/1.0/developer_manual/maia_dev/development_workflow.html @@ -0,0 +1,296 @@ + + + + + + + + + + Development workflow — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Development workflow

+
+

Sub-modules

+

The Maia repository is compatible with the development process described here. It uses git submodules to ease the joint development with other repositories compatible with this organization.

+

TL;DR: configure the git repository by sourcing this file and then execute:

+
cd $MAIA_FOLDER
+git submodule update --init
+git_config_submodules
+
+
+
+
+

Launch tests

+

Tests can be launched by calling CTest, but during the development, we often want to parameterize how to run tests (which ones, number of processes, verbosity level…).

+

There is a source.sh generated in the build/ folder. It can be sourced in order to get the correct environment to launch the tests (notably, it updates LD_LIBRARY_PATH and PYTHONPATH to point to build artifacts).

+

Tests can be called with e.g.:

+
cd $PROJECT_BUILD_DIR
+source source.sh
+mpirun -np 4 external/std_e/std_e_unit_tests
+./external/cpp_cgns/cpp_cgns_unit_tests
+mpirun -np 4 test/maia_doctest_unit_tests
+mpirun -np 4 pytest $PROJECT_SRC_DIR/maia --with-mpi
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/genindex.html b/docs/1.0/genindex.html new file mode 100644 index 00000000..11b95472 --- /dev/null +++ b/docs/1.0/genindex.html @@ -0,0 +1,520 @@ + + + + + + + + + + Index — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Index
  • + + +
  • + + + +
  • + +
+ + +
+
+
+
+ + +

Index

+ +
+ C + | D + | E + | F + | G + | I + | L + | M + | N + | P + | Q + | R + | S + | T + | V + | W + | Z + +
+

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

I

+ + + +
+ +

L

+ + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

P

+ + + +
+ +

Q

+ + +
+ +

R

+ + + +
+ +

S

+ + +
+ +

T

+ + +
+ +

V

+ + +
+ +

W

+ + + +
+ +

Z

+ + +
+ + + +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/index.html b/docs/1.0/index.html new file mode 100644 index 00000000..f0aa554a --- /dev/null +++ b/docs/1.0/index.html @@ -0,0 +1,273 @@ + + + + + + + + + + Welcome to Maia! — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Welcome to Maia!

+

Maia is a C++ and Python library for parallel algorithms over CGNS trees: distributed IO, partitioning, data management and various kind of transformations (generation of faces, destructuration, extractions, …).

+

Maia is an open source software developed at ONERA. +Associated source repository and issue tracking are hosted on Gitlab.

+
+
+

Documentation summary

+

Quick start is the perfect page for a first overview of Maia or to retrieve the environments to use on Onera’s clusters.

+

Introduction details the extensions made to the CGNS standard in order to define parallel CGNS trees.

+

User Manual is the main part of this documentation. It describes most of the high level APIs provided by Maia.

+

Developer Manual (under construction) provides more details on some algorithms and can be consulted if you want to contribute to Maia.

+
+
+
+
+
+
+ + +
+ +
+
+ + +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/installation.html b/docs/1.0/installation.html new file mode 100644 index 00000000..b6a377c9 --- /dev/null +++ b/docs/1.0/installation.html @@ -0,0 +1,427 @@ + + + + + + + + + + Installation — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Installation

+
+

Prefered installation procedure

+

Maia depends on quite a few libraries of different kinds, be it system libraries like MPI, third-party libraries like HDF5, ONERA libraries like ParaDiGM and Cassiopée, git submodules (std_e…), or Python modules (mpi4py, ruamel). The prefered way of installing Maia in a coherent environment is by using the Spack package manager. A Spack recipe for Maia can be found on the ONERA Spack repository.

+
+

Installation through Spack

+
    +
  1. Source a Spack repository on your machine.

  2. +
  3. If you don’t have a Spack repository ready, you can download one with git clone https://github.com/spack/spack.git. On ONERA machines, it is advised to use the Spacky helper.

  4. +
  5. Download the ONERA Spack repository with git clone https://gitlab.onera.net/informatics/infra/onera_spack_repo.git

  6. +
  7. Tell Spack that package recipes are in onera_spack_repo by adding the following lines to $SPACK_ROOT/etc/repos.yaml:

  8. +
+
repos:
+- path/to/onera_spack_repo
+
+
+

(note that spacky does steps 3. and 4. for you)

+
    +
  1. You should be able to see the package options of Maia with spack info maia

  2. +
  3. To install Maia: spack install maia

  4. +
+
+
+

Development workflow

+

For development, it is advised to use Spack to have Maia dependencies, but then follow a typical CMake workflow with cmake/make.

+
+

Dependencies

+

To get access to Maia dependencies in your development environment, you can:

+
    +
  • Install a Spack version of Maia, source it in your development environment to get all the dependencies, then override with your own compiled version of Maia

  • +
  • Do the same, but use a Spack environment containing Maia instead of just the Maia package

  • +
  • Source a Spack environment where Maia has been removed from the environment view. This can be done by adding the following lines to the spack.yaml environement file:

  • +
+
view:
+  default:
+    exclude: ['maia']
+
+
+

This last option is cleaner because you are sure that you are not using another version of Maia (but it means you need to create or have access to such an environment view)

+
+
+

Source the build folder

+

You can develop without the need to install Maia. However, in addition to sourcing your dependencies in your development environment, you also need to source the build artifacts by:

+
cd $MAIA_BUILD_FOLDER
+source source.sh
+
+
+

The source.sh file is created by CMake and will source all Maia artifacts (dynamic libraries, python modules…)

+
+
+
+

Development workflow with submodules

+

It is often practical to develop Maia with some of its dependencies, namely:

+
    +
  • project_utils

  • +
  • std_e

  • +
  • cpp_cgns

  • +
  • paradigm

  • +
  • pytest_parallel

  • +
+

For that, you need to use git submodules. Maia submodules are located at $MAIA_FOLDER/external. To populate them, use git submodule update --init. Once done, CMake will use these versions of the dependencies. If you don’t populate the submodules, CMake will try to use the ones of your environment (for instance, the one installed by Spack).

+

We advise that you use some additional submodule configuration utilities provided in this file. In particular, you should use:

+
cd $MAIA_FOLDER
+git submodule update --init
+git_config_submodules
+
+
+

The detailed meaning of git_config_submodules and the git submodule developper workflow of Maia is presented here.

+

If you are using Maia submodules, you can filter them out from your Spack environment view like so:

+
view:
+  default:
+    exclude: ['maia','std-e','cpp-cgns','paradigm','pytest_parallel']
+
+
+
+
+
+

Manual installation procedure

+
+

Dependencies

+

Maia depends on:

+
    +
  • python3

  • +
  • MPI

  • +
  • hdf5

  • +
  • Cassiopée

  • +
  • pytest >6 (python package)

  • +
  • ruamel (python package)

  • +
  • mpi4py (python package)

  • +
+

The build process requires:

+
    +
  • Cmake >= 3.14

  • +
  • GCC >= 8 (Clang and Intel should work but no CI)

  • +
+
+

Other dependencies

+

During the build process, several other libraries will be downloaded:

+
    +
  • pybind11

  • +
  • range-v3

  • +
  • doctest

  • +
  • ParaDiGM

  • +
  • project_utils

  • +
  • std_e

  • +
  • cpp_cgns

  • +
+

This process should be transparent.

+
+
+

Optional dependencies

+

The documentation build requires:

+
    +
  • Doxygen >= 1.8.19

  • +
  • Breathe >= 4.15 (python package)

  • +
  • Sphinx >= 3.00 (python package)

  • +
+
+
+
+

Build and install

+
    +
  1. Install the required dependencies. They must be in your environment (PATH, LD_LIBRARY_PATH, PYTHONPATH).

  2. +
+
+

For pytest, you may need these lines :

+
+
pip3 install --user pytest
+pip3 install --user pytest-mpi
+pip3 install --user pytest-html
+pip3 install --user pytest_check
+pip3 install --user ruamel.yaml
+
+
+
    +
  1. Then you need to populate your external folder. You can do it with git submodule update --init

  2. +
  3. Then use CMake to build maia, e.g.

  4. +
+
SRC_DIR=<path to source repo>
+BUILD_DIR=<path to tmp build dir>
+INSTALL_DIR=<path to where you want to install Maia>
+cmake -S $SRC_DIR -B$BUILD_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR
+cd $BUILD_DIR && make -j && make install
+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/introduction/introduction.html b/docs/1.0/introduction/introduction.html new file mode 100644 index 00000000..259372c9 --- /dev/null +++ b/docs/1.0/introduction/introduction.html @@ -0,0 +1,510 @@ + + + + + + + + + + Introduction — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Introduction

+

These section introduces the core concepts of distributed and partitioned data +used in Maia, and their application to define parallel CGNS trees.

+
+

Core concepts

+
+

Dividing data

+

Global data is the complete data that describes an object. Let’s represent it as the +following ordered shapes:

+../_images/data_full.svg

Now imagine that you want to split this data into N pieces (N=3 for the next illustrations). +Among all the possibilities, we distinguishes two ways to do it:

+
    +
  1. Preserving order: we call such repartition distributed data, and we use the term block +to refer to a piece of this distributed data.

  2. +
+
+
../_images/data_dist.svg

Several distributions are possible, depending on where data is cut, but they all share the same properties:

+
+
    +
  • the original order is preserved across the distributed data,

  • +
  • each element appears in one and only one block,

  • +
  • a block can be empty as long as the global order is preserved (b).

  • +
+
+
+
    +
  1. Taking arbitrary subsets of the original data: we call such subsets partitioned data, and we use the term partition +to refer to a piece of this partitioned data.

  2. +
+
+
../_images/data_part.svg

Due to this relaxed constraint, there is much more admissible splits since we allow in our definition the following cases:

+
+
    +
  • an element can appear in several partitions, or several times within the same partition (b),

  • +
  • it is allowed that an element does not appear in a partition (c).

  • +
+
+

Such repartitions are often useful when trying to gather the elements depending on +some characteristics: on the above example, we created the partition of squared shaped elements, round shaped +elements and unfilled elements (b). Thus, some elements belong to more than one partition.

+
+

A key point is that no absolute best way of diving data: depending of what we want to do with the +data, one of the way to divide it may be more adapted. In the previous example:

+
    +
  • distributed data is fine if you want to count the number of filled shapes: you can count in each +block and then sum the result over the blocks.

  • +
  • Now assume that you want to renumber the elements depending on their shape, then on their color: +if partitioned data (b) is used, partitions 1 and 2 could independently order +their elements by color since they are already sorted by shape 1.

  • +
+
+
+

Numberings

+

In order to describe the link between our divisions and the original global data, we need to +define additional concepts.

+

For distributed data, since the original ordering is respected, the link with the global data is totally implicit: +we just need to know the number of elements in each block or, equivalently, the distribution array +of the data. This is an array of size N+1 indicating the bounds of each block. By convention, distribution +array starts at 0 and uses semi-open intervals.

+../_images/data_dist_gnum.svg

With this information, the global number of the jth element in the ith block is given by +\(\mathtt{dist[i] + j + 1}\).

+

On the contrary, for partitioned data, we have to explicitly store the link with the global data: +we use a local to global numbering array (often called LN_to_GN for short). +Each partition has its own LN_to_GN array whose size is the number of elements in the partition.

+../_images/data_part_gnum.svg

Then, the global number of the jth element in the ith partition is simply given by +\(\mathtt{LN\_to\_GN[i][j]}\).

+

For any global data, these additional informations allow to create a mapping beetween global data, partitioned +data and distributed data. Thus, it is always possible to reconstruct one of the views from another one.

+
+
+

Application to MPI parallelism

+

The concepts introduced above make all sense in the context of distributed memory computers. +In such architecture, the global data is in fact never detained by a single proc (it would be too heavy): +we always use a distributed or partitioned view of this data. +Using a good repartition is often the key of a well balanced algorithm.

+

In the distributed view, we produce as much blocks as the number of MPI processes. Each process holds +its own block, and the distribution array, of size n_rank+1, is know by each process.

+

In the partitioned view, we often produce one partition per process; but it can sometime be useful to +put several partitions on a given process. Each process holds the data associated to its partition, +including the related LN_to_GN arrays (LN_to_GN related to the other partitions +are not know by the current process).

+

The ParaDiGM library provides some low level methods to exchange data between the partitioned and +distributed views in a MPI context, with options to deal with redundant elements, create new distributions, +manage variable strides, etc.

+
+
+
+

Application to meshes

+

Until now, we have seen the definition of distributed and partitioned data for a single array +of global data. Let see how to apply these concepts to a basic CFD mesh.

+

Consider the following 12-vertices (green numbers), 6-quads (purple numbers) mesh and +assume that it is described by a cell to vertex connectivity. Thus, the mesh file defines +3 global arrays:

+
    +
  • the CoordinateX and CoordinateY arrays, each one of size 12

  • +
  • the Connectivity array of size 6*4 = 24

  • +
+../_images/full_mesh.svg

If we have 2 processes at our disposal, a parallel way to load this mesh is to +distribute all the vertex-related entities with a distribution array of [0,6,12] +and all the element-related entities with a distribution array of [0,3,6] 2:

+../_images/dist_mesh_arrays.svg

Then, the blue part of the arrays will be stored on the first process and the red part on the second process. +Looking back at the original mesh, this correspond to the following distributed meshes:

+../_images/dist_mesh.svg

with the blue entities stored on the first process, and the red ones on the second process.

+

Notice that the distributed mesh is not suited for solver computation. For instance, cells on the domain treated by the first process are not connex. Worst yet, vertex and element quantities are distributed independently. Take a look at cell 1: it is blue so it belongs to process 0. However,its vertices 9 and 10 are on process 1, this means that there coordinates are not accessible directly on process 0. For these reasons, we want to properly partition our mesh, such as this:

+../_images/part_mesh.svg../_images/part_mesh_arrays.svg

Now we have two semi-independent meshes and we can reason about each element with all its associated data +present on the same process. This partitioned view of the mesh has the two following properties:

+
+
    +
  • Coherency: every data array is addressable locally,

  • +
  • Connexity: the data represents geometrical entities that define a local subregion of the mesh.

  • +
+
+

We want to keep the link between the base mesh and its partitioned version. For that, we need to store global numbering arrays, quantity by quantity:

+../_images/dist_part_LN_to_GN.svg

For example, for sub-mesh 0, we can check that element number 3 is actually element number 6 of the original mesh. +We can also see that vertex 3 of sub-mesh 0, and vertex 1 of submesh 1 are actually the same, that is, vertex 4 of the global mesh.

+

Thanks to these global numbering, any quantity computed on the partitioned meshes (such as a vertex based field) +can then be transfered to the corresponding distributed vertex, before writting back the results.

+
+
+

Maia CGNS trees

+
+

Overview

+

Since Python/CGNS trees describe CFD meshes, we can apply the previous concepts to +define parallel trees.

+

A full tree is a tree as it is inside a CGNS file, or how it would be loaded by only one process. A full tree is global data.

+

A dist tree is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. See Distributed trees.

+

A part tree is a partial tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. See Partitioned trees.

+

A size tree is a tree in which only the size of the data is stored. A size tree is typically global data because each process needs it to know which block of data it will have to load and store.

+

([Legacy] A skeleton tree is a collective tree in which fields and element connectivities are not loaded)

+

As explained in previous section, the standard full tree does not make sense in the context of a MPI parallel +application: all the trees encountered within the execution of maia are distributed trees or partitioned trees. +The next section describe the specification of these trees.

+
+
+

Specification

+

Let us use the following tree as an example:

+../_images/tree_seq.png +

This tree is a global tree. It may appear like that on a HDF5/CGNS file, or if loaded entirely on one process as a Python/CGNS tree.

+
+

Distributed trees

+

A dist tree is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array.

+

If we distribute our tree over two processes, we would then have something like that:

+../_images/dist_tree.png +

Let us look at one of them and annotate nodes specific to the distributed tree:

+../_images/dist_tree_expl.png +

Arrays of non-constant size are distributed: fields, connectivities, PointLists. +Others (PointRanges, CGNSBase_t and Zone_t dimensions…) are of limited size and therefore replicated on all processes with virtually no memory penalty.

+

On each process, for each entity kind, a partial distribution is stored, that gives information of which block of the arrays are stored locally.

+

For example, for process 0, the distribution array of vertices of MyZone is located at MyBase/MyZone/Distribution/Vertex and is equal to [0, 9, 18]. It means that only indices in the semi-open interval [0 9) are stored by the dist tree on this process, and that the total size of the array is 18. +This partial distribution applies to arrays spaning all the vertices of the zone, e.g. CoordinateX.

+

More formally, a partial distribution related to an entity kind E is an array [start,end,total_size] of 3 integers where [start:end) is a closed/open interval giving, for all global arrays related to E, the sub-array that is stored locally on the distributed tree, and total_size is the global size of the arrays related to E.

+

The distributed entities are:

+
+
Vertices and Cells

The partial distribution are stored in Distribution/Vertex and Distribution/Cell nodes at the level of the Zone_t node.

+

Used for example by GridCoordinates_t and FlowSolution_t nodes if they do not have a PointList (i.e. if they span the entire vertices/cells of the zone)

+
+
Quantities described by a PointList or PointRange

The partial distribution is stored in a Distribution/Index node at the level of the PointList/PointRange

+

For example, ZoneSubRegion_t and BCDataSet_t nodes.

+

If the quantity is described by a PointList, then the PointList itself is distributed the same way (in contrast, a PointRange is fully replicated across processes because it is lightweight)

+
+
Connectivities

The partial distribution is stored in a Distribution/Element node at the level of the Element_t node. Its values are related to the elements, not the vertices of the connectivity array.

+

If the element type is heterogenous (NGon, NFace or MIXED) a Distribution/ElementConnectivity is also present, and this partial distribution is related to the ElementConnectivity array.

+
+
+
+

Note

+

A distributed tree object is not norm-compliant since most of its arrays are partial: on the previous example, +CoordinateX array on rank 0 has a length of 9 when MyZone declares 18 vertices. +However, the union of all the distributed tree objects represents a norm-compliant CGNS tree.

+
+
+
+

Partitioned trees

+

A part tree is a partial CGNS tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process.

+

If we take the global tree from before and partition it, we may get the following tree:

+../_images/part_tree.png +

If we annotate the first one:

+../_images/part_tree_expl.png +

A part tree is just a regular, norm-compliant tree with additional information (in the form of GlobalNumbering nodes) that keeps the link with the unpartitioned tree it comes from. Notice that the tree structure is not necessarily the same across all processes.

+

The GlobalNumbering nodes are located at the same positions that the Distribution nodes were in the distributed tree.

+

A GlobalNumbering contains information to link an entity in the partition to its corresponding entity in the original tree. For example, the element section Hexa has a global numbering array of value [3 4]. It means:

+
    +
  • Since it is an array of size 2, there is 2 elements in this section (which is confirmed by the ElementRange) ,

  • +
  • The first element was the element of id 3 in the original mesh,

  • +
  • The second element was element 4 in the original mesh.

  • +
+
+
+

Naming conventions

+

When partitioning, some nodes are split, so there is convention to keep track of the fact they come from the same original node:

+
    +
  • Zone_t nodes : MyZone is split in MyZone.PX.NY where X is the rank of the process, and Y is the id of the zone on process X.

  • +
  • Splitable nodes (notably GC_t) : MyNode is split in MyNode.N. They appear in the following scenario:

    +
      +
    • We partition for 3 processes

    • +
    • Zone0 is connected to Zone1 through GridConnectivity_0_to_1

    • +
    • Zone0 is not split (but goes to process 0 and becomes Zone0.P0.N0). Zone1 is split into Zone1.P1.N0 and Zone1.P2.N0. Then GridConnectivity_0_to_1 of Zone0 must be split into GridConnectivity_0_to_1.1 and GridConnectivity_0_to_1.2.

    • +
    +
  • +
+

Note that partitioning may induce new GC_t internal to the original zone being splitted. Their name is implementation-defined and those nodes do not have a GlobalNumbering since they did not exist in the original mesh.

+
+
+
+

Maia trees

+

A CGNS tree is said to be a Maia tree if it has the following properties:

+
    +
  • For each unstructured zone, the ElementRange of all Elements_t sections

    +
      +
    • are contiguous

    • +
    • are ordered by ascending dimensions (i.e. edges come first, then faces, then cells)

    • +
    • the first section starts at 1

    • +
    • there is at most one section by element type (e.g. not possible to have two QUAD_4 sections)

    • +
    +
  • +
+

Notice that this is property is required by some functions of Maia, not all of them!

+

A Maia tree may be a global tree, a distributed tree or a partitioned tree.

+

Footnotes

+
+
1
+

Actually, such algorithm could be written for distributed data, but is less intuitive and require more knowledge of what +if happening on the other blocks.

+
+
2
+

Other distributions are possible : we could, for example, affect all the CoordinateX array on the first process (using the distribution array [0,12,12]) and the CoordinateY array on the second, but we would have to manage a different distribution for each array.

+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/license.html b/docs/1.0/license.html new file mode 100644 index 00000000..3de45cbb --- /dev/null +++ b/docs/1.0/license.html @@ -0,0 +1,679 @@ + + + + + + + + + + License — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

License

+
+

Mozilla Public License Version 2.0

+
+
+

1. Definitions

+
+
1.1. “Contributor”

means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software.

+
+
1.2. “Contributor Version”

means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor’s Contribution.

+
+
1.3. “Contribution”

means Covered Software of a particular Contributor.

+
+
1.4. “Covered Software”

means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof.

+
+
1.5. “Incompatible With Secondary Licenses”

means

+
+
+
    +
  • +
    (a) that the initial Contributor has attached the notice described

    in Exhibit B to the Covered Software; or

    +
    +
    +
  • +
  • +
    (b) that the Covered Software was made available under the terms of

    version 1.1 or earlier of the License, but not also under the +terms of a Secondary License.

    +
    +
    +
  • +
+
+
1.6. “Executable Form”

means any form of the work other than Source Code Form.

+
+
1.7. “Larger Work”

means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software.

+
+
1.8. “License”

means this document.

+
+
1.9. “Licensable”

means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License.

+
+
1.10. “Modifications”

means any of the following:

+
+
+
    +
  • +
    (a) any file in Source Code Form that results from an addition to,

    deletion from, or modification of the contents of Covered +Software; or

    +
    +
    +
  • +
  • +
    (b) any new file in Source Code Form that contains any Covered

    Software.

    +
    +
    +
  • +
+
+
1.11. “Patent Claims” of a Contributor

means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version.

+
+
1.12. “Secondary License”

means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses.

+
+
1.13. “Source Code Form”

means the form of the work preferred for making modifications.

+
+
1.14. “You” (or “Your”)

means an individual or a legal entity exercising rights under this +License. For legal entities, “You” includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, “control” means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity.

+
+
+
+
+

2. License Grants and Conditions

+
+

2.1. Grants

+

Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license:

+
    +
  • +
    (a) under intellectual property rights (other than patent or trademark)

    Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and

    +
    +
    +
  • +
  • +
    (b) under Patent Claims of such Contributor to make, use, sell, offer

    for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version.

    +
    +
    +
  • +
+
+
+

2.2. Effective Date

+

The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution.

+
+
+

2.3. Limitations on Grant Scope

+

The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor:

+
    +
  • +
    (a) for any code that a Contributor has removed from Covered Software;

    or

    +
    +
    +
  • +
  • +
    (b) for infringements caused by: (i) Your and any other third party’s

    modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or

    +
    +
    +
  • +
  • +
    (c) under Patent Claims infringed by Covered Software in the absence of

    its Contributions.

    +
    +
    +
  • +
+

This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4).

+
+
+

2.4. Subsequent Licenses

+

No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3).

+
+
+

2.5. Representation

+

Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License.

+
+
+

2.6. Fair Use

+

This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents.

+
+
+

2.7. Conditions

+

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1.

+
+
+
+

3. Responsibilities

+
+

3.1. Distribution of Source Form

+

All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients’ rights in the Source Code +Form.

+
+
+

3.2. Distribution of Executable Form

+

If You distribute Covered Software in Executable Form then:

+
    +
  • +
    (a) such Covered Software must also be made available in Source Code

    Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and

    +
    +
    +
  • +
  • +
    (b) You may distribute such Executable Form under the terms of this

    License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients’ rights in the Source Code Form under this License.

    +
    +
    +
  • +
+
+
+

3.3. Distribution of a Larger Work

+

You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s).

+
+
+

3.4. Notices

+

You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies.

+
+
+

3.5. Application of Additional Terms

+

You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction.

+
+
+
+

4. Inability to Comply Due to Statute or Regulation

+

If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it.

+
+
+

5. Termination

+

5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice.

+

5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate.

+

5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination.

+
+
+

6. Disclaimer of Warranty

+
+

Covered Software is provided under this License on an “as is” +basis, without warranty of any kind, either expressed, implied, or +statutory, including, without limitation, warranties that the +Covered Software is free of defects, merchantable, fit for a +particular purpose or non-infringing. The entire risk as to the +quality and performance of the Covered Software is with You. +Should any Covered Software prove defective in any respect, You +(not any Contributor) assume the cost of any necessary servicing, +repair, or correction. This disclaimer of warranty constitutes an +essential part of this License. No use of any Covered Software is +authorized under this License except under this disclaimer.

+
+
+
+

7. Limitation of Liability

+
+

Under no circumstances and under no legal theory, whether tort +(including negligence), contract, or otherwise, shall any +Contributor, or anyone who distributes Covered Software as +permitted above, be liable to You for any direct, indirect, +special, incidental, or consequential damages of any character +including, without limitation, damages for lost profits, loss of +goodwill, work stoppage, computer failure or malfunction, or any +and all other commercial damages or losses, even if such party +shall have been informed of the possibility of such damages. This +limitation of liability shall not apply to liability for death or +personal injury resulting from such party’s negligence to the +extent applicable law prohibits such limitation. Some +jurisdictions do not allow the exclusion or limitation of +incidental or consequential damages, so this exclusion and +limitation may not apply to You.

+
+
+
+

8. Litigation

+

Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party’s ability to bring +cross-claims or counter-claims.

+
+
+

9. Miscellaneous

+

This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor.

+
+
+

10. Versions of the License

+
+

10.1. New Versions

+

Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number.

+
+
+

10.2. Effect of New Versions

+

You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward.

+
+
+

10.3. Modified Versions

+

If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License).

+
+
+

10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses

+

If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached.

+
+
+
+

Exhibit A - Source Code Form License Notice

+
+

This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/.

+
+

If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice.

+

You may add additional accurate notices of copyright ownership.

+
+
+

Exhibit B - “Incompatible With Secondary License” Notice

+
+

This Source Code Form is “Incompatible With Secondary Licenses”, as +defined by the Mozilla Public License, v. 2.0.

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/objects.inv b/docs/1.0/objects.inv new file mode 100644 index 00000000..7697d566 Binary files /dev/null and b/docs/1.0/objects.inv differ diff --git a/docs/1.0/quick_start.html b/docs/1.0/quick_start.html new file mode 100644 index 00000000..a3f5e389 --- /dev/null +++ b/docs/1.0/quick_start.html @@ -0,0 +1,447 @@ + + + + + + + + + + Quick start — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Quick start

+
+

Environnements

+

We provide ready-to-go environments including Maia and its dependencies on the following clusters:

+

Spiro-EL8

+

This is the recommended environment for standalone usage of Maia. It works with intel mpi library (2021) +and python version 3.9.

+
module purge
+source /scratchm/sonics/dist/spiro_el8.sh --compiler=gcc@12 --mpi=impi
+
+export MAIA_HOME=/scratchm/jcoulet/aa_install_py3/maia/opt-impi21
+export LD_LIBRARY_PATH=$MAIA_HOME/lib:$LD_LIBRARY_PATH
+export PYTHONPATH=$MAIA_HOME/lib/python3.9/site-packages:$PYTHONPATH
+
+
+

If you want to use maia in elsA framework, the next installation is compatible with +elsA spiro-el8_mpi production :

+
module purge
+source /stck/elsa/Public/v5.1.03/Dist/bin/spiro-el8_mpi/source.me
+
+export MAIA_HOME=/scratchm/jcoulet/aa_install_py3/maia/opt-cfd5_21/
+export LD_LIBRARY_PATH=$MAIA_HOME/lib:$LD_LIBRARY_PATH
+export PYTHONPATH=$MAIA_HOME/lib/python3.7/site-packages:$PYTHONPATH
+
+
+

Sator

+

Sator’s version is compiled with intel mpi library and support of large integers.

+
module purge
+source /tmp_user/sator/sonics/source-intel-oneapi-2021.2-sator-centos8.me --compiler gcc8.3
+
+export MAIA_HOME=/tmp_user/sator/jcoulet/opt/maia
+export LD_LIBRARY_PATH=$MAIA_HOME/lib:$LD_LIBRARY_PATH
+export PYTHONPATH=$MAIA_HOME/lib/python3.8/site-packages:$PYTHONPATH
+
+
+

If you prefer to build your own version of Maia, see Installation section.

+
+
+

Supported meshes

+

Maia supports CGNS meshes from version 4.2, meaning that polyhedral connectivities (NGON_n, NFACE_n +and MIXED nodes) must have the ElementStartOffset node.

+

Former meshes can be converted with the (sequential) maia_poly_old_to_new script included +in the $PATH once the environment is loaded:

+
$> maia_poly_old_to_new mesh_file.hdf
+
+
+

The opposite maia_poly_new_to_old script can be used to put back meshes +in old conventions, insuring compatibility with legacy tools.

+
+

Warning

+

CGNS databases should respect the SIDS. +The most commonly observed non-compliant practices are:

+
    +
  • Empty DataArray_t (of size 0) under FlowSolution_t containers.

  • +
  • 2D shaped (N1,N2) DataArray_t under BCData_t containers. +These arrays should be flat (N1xN2,).

  • +
  • Implicit BCDataSet_t location for structured meshes: if GridLocation_t +and PointRange_t of a given BCDataSet_t differs from the +parent BC_t node, theses nodes should be explicitly defined at BCDataSet_t +level.

  • +
+

Several non-compliant practices can be detected with the cgnscheck utility. Do not hesitate +to check your file if Maia is unable to read it.

+
+

Note also that ADF files are not supported; CGNS files should use the HDF binary format. ADF files can +be converted to HDF thanks to cgnsconvert.

+
+
+

Highlights

+
+

Tip

+

Download sample files of this section: +S_twoblocks.cgns, +U_ATB_45.cgns

+
+

Daily user-friendly pre & post processing

+

Maia provides simple Python APIs to easily setup pre or post processing operations: +for example, converting a structured tree into an unstructured (NGon) tree +is as simple as

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia
+
+tree_s = maia.io.file_to_dist_tree('S_twoblocks.cgns', comm)
+tree_u = maia.algo.dist.convert_s_to_ngon(tree_s, comm)
+maia.io.dist_tree_to_file(tree_u, 'U_twoblocks.cgns', comm)
+
+
+_images/qs_basic.png +

In addition of being parallel, the algorithms are as much as possible topologic, meaning that +they do not rely on a geometric tolerance. This also allow us to preserve the boundary groups +included in the input mesh (colored on the above picture).

+

Building efficient workflows

+

By chaining this elementary blocks, you can build a fully parallel advanced workflow +running as a single job and minimizing file usage.

+

In the following example, we load an angular section of the +ATB case, +duplicate it to a 180° case, split it, and perform some slices and extractions.

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia.pytree as PT
+import maia
+
+# Read the file. Tree is distributed
+dist_tree = maia.io.file_to_dist_tree('U_ATB_45.cgns', comm)
+
+# Duplicate the section to a 180° mesh
+# and merge all the blocks into one
+opposite_jns = [['Base/bump_45/ZoneGridConnectivity/matchA'],
+                ['Base/bump_45/ZoneGridConnectivity/matchB']]
+maia.algo.dist.duplicate_from_periodic_jns(dist_tree,
+    ['Base/bump_45'], opposite_jns, 22, comm)
+maia.algo.dist.merge_connected_zones(dist_tree, comm)
+
+# Split the mesh to have a partitioned tree
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Create a ZSR as well
+for part_zone in PT.get_all_Zone_t(part_tree):
+  if PT.get_node_from_name_and_label(part_zone, 'wall', 'BC_t') is not None:
+    PT.new_ZoneSubRegion(name='ZSRWall', bc_name='wall', parent=part_zone)
+
+# Now we can call some partitioned algorithms
+maia.algo.part.compute_wall_distance(part_tree, comm, 
+    families=['WALL'], point_cloud='Vertex')
+extract_tree = maia.algo.part.extract_part_from_zsr(part_tree, "ZSRWall", comm)
+slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0], comm,
+      containers_name=['WallDistance'])
+
+# Merge extractions in a same tree in order to save it
+base = PT.get_child_from_label(slice_tree, 'CGNSBase_t')
+PT.set_name(base, f'PlaneSlice')
+PT.add_child(extract_tree, base)
+maia.algo.pe_to_nface(dist_tree,comm)
+
+extract_tree_dist = maia.factory.recover_dist_tree(extract_tree, comm)
+maia.io.dist_tree_to_file(extract_tree_dist, 'ATB_extract.cgns', comm)
+
+
+_images/qs_workflow.png +

The above illustration represents the input mesh (gray volumic block) and the +extracted surfacic tree (plane slice and extracted wall BC). Curved lines are +the outline of the volumic mesh after duplication.

+

Compliant with the pyCGNS world

+

Finally, since Maia uses the standard CGNS/Python mapping, +you can set up applications involving multiple python packages: +here, we create and split a mesh with maia, but we then call Cassiopee functions +to compute the gradient of a field on each partition.

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia
+import Transform.PyTree as CTransform
+import Converter.PyTree as CConverter
+import Post.PyTree      as CPost
+
+dist_tree = maia.factory.generate_dist_block([101,6,6], 'TETRA_4', comm)
+CTransform._scale(dist_tree, [5,1,1], X=(0,0,0))
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+CConverter._initVars(part_tree, '{Field}=sin({nodes:CoordinateX})')
+part_tree = CPost.computeGrad(part_tree, 'Field')
+
+maia.transfer.part_tree_to_dist_tree_all(dist_tree, part_tree, comm)
+maia.io.dist_tree_to_file(dist_tree, 'out.cgns', comm)
+
+
+_images/qs_pycgns.png +

Be aware that other tools often expect to receive geometrically +consistent data, which is why we send the partitioned tree to +Cassiopee functions. The outline of this partitions (using 4 processes) +are materialized by the white lines on the above figure.

+
+
+

Resources and Troubleshouting

+

The user manual describes most of the high level APIs provided by Maia. +If you want to be comfortable with the underlying concepts of distributed and +partitioned trees, have a look at the introduction section.

+

The user manual is illustrated with basic examples. Additional +test cases can be found in +the sources.

+

Issues can be reported on +the gitlab board +and help can also be asked on the dedicated +Element room.

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/related_projects.html b/docs/1.0/related_projects.html new file mode 100644 index 00000000..f6cfef5a --- /dev/null +++ b/docs/1.0/related_projects.html @@ -0,0 +1,282 @@ + + + + + + + + + + Related projects — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/releases/release_notes.html b/docs/1.0/releases/release_notes.html new file mode 100644 index 00000000..09ad7f59 --- /dev/null +++ b/docs/1.0/releases/release_notes.html @@ -0,0 +1,274 @@ + + + + + + + + + + Release notes — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Release notes

+

This page contains information about what has changed in each new version of maia.

+
+

v0.1.0 (2021)

+

First documented release

+
+

Features

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/releases/v0.1.0.html b/docs/1.0/releases/v0.1.0.html new file mode 100644 index 00000000..78587dca --- /dev/null +++ b/docs/1.0/releases/v0.1.0.html @@ -0,0 +1,258 @@ + + + + + + + + + + v0.1.0 (2021) — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

v0.1.0 (2021)

+

First documented release

+
+

Features

+
+
+ + +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/search.html b/docs/1.0/search.html new file mode 100644 index 00000000..a93b9c6e --- /dev/null +++ b/docs/1.0/search.html @@ -0,0 +1,268 @@ + + + + + + + + + + Search — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Search
  • + + +
  • + +
  • + +
+ + +
+
+
+
+ + + + +
+ +
+ +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/searchindex.js b/docs/1.0/searchindex.js new file mode 100644 index 00000000..861259da --- /dev/null +++ b/docs/1.0/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["developer_manual/algo_description","developer_manual/algo_description/elements_to_ngons","developer_manual/developer_manual","developer_manual/logging","developer_manual/maia_dev/conventions","developer_manual/maia_dev/development_workflow","index","installation","introduction/introduction","license","quick_start","related_projects","releases/release_notes","releases/v0.1.0","user_manual/algo","user_manual/config","user_manual/factory","user_manual/io","user_manual/transfer","user_manual/user_manual"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["developer_manual/algo_description.rst","developer_manual/algo_description/elements_to_ngons.rst","developer_manual/developer_manual.rst","developer_manual/logging.rst","developer_manual/maia_dev/conventions.rst","developer_manual/maia_dev/development_workflow.rst","index.rst","installation.rst","introduction/introduction.rst","license.rst","quick_start.rst","related_projects.rst","releases/release_notes.rst","releases/v0.1.0.rst","user_manual/algo.rst","user_manual/config.rst","user_manual/factory.rst","user_manual/io.rst","user_manual/transfer.rst","user_manual/user_manual.rst"],objects:{"":[[16,0,1,"","dump_pdm_output"],[16,0,1,"","graph_part_tool"],[16,0,1,"","part_interface_loc"],[16,0,1,"","preserve_orientation"],[16,0,1,"","reordering"],[16,0,1,"","zone_to_parts"]],"maia.algo":[[14,1,1,"","nface_to_pe"],[14,1,1,"","pe_to_nface"],[14,1,1,"","transform_affine"]],"maia.algo.dist":[[14,1,1,"","conformize_jn_pair"],[14,1,1,"","convert_elements_to_mixed"],[14,1,1,"","convert_elements_to_ngon"],[14,1,1,"","convert_mixed_to_elements"],[14,1,1,"","convert_s_to_u"],[14,1,1,"","duplicate_from_rotation_jns_to_360"],[14,1,1,"","generate_jns_vertex_list"],[14,1,1,"","merge_connected_zones"],[14,1,1,"","merge_zones"],[14,1,1,"","merge_zones_from_family"],[14,1,1,"","ngons_to_elements"],[14,1,1,"","rearrange_element_sections"],[14,1,1,"","redistribute_tree"]],"maia.algo.part":[[14,1,1,"","centers_to_nodes"],[14,1,1,"","compute_cell_center"],[14,1,1,"","compute_edge_center"],[14,1,1,"","compute_face_center"],[14,1,1,"","compute_wall_distance"],[14,1,1,"","extract_part_from_bc_name"],[14,1,1,"","extract_part_from_zsr"],[14,1,1,"","find_closest_points"],[14,1,1,"","interpolate_from_part_trees"],[14,1,1,"","iso_surface"],[14,1,1,"","localize_points"],[14,1,1,"","plane_slice"],[14,1,1,"","spherical_slice"]],"maia.algo.seq":[[14,1,1,"","enforce_ngon_pe_local"],[14,1,1,"","poly_new_to_old"],[14,1,1,"","poly_old_to_new"]],"maia.factory":[[16,1,1,"","distribute_tree"],[16,1,1,"","generate_dist_block"],[16,1,1,"","partition_dist_tree"],[16,1,1,"","recover_dist_tree"]],"maia.factory.partitioning":[[16,1,1,"","compute_balanced_weights"],[16,1,1,"","compute_nosplit_weights"],[16,1,1,"","compute_regular_weights"]],"maia.io":[[17,1,1,"","dist_tree_to_file"],[17,1,1,"","file_to_dist_tree"],[17,1,1,"","part_tree_to_file"],[17,1,1,"","read_tree"],[17,1,1,"","write_tree"],[17,1,1,"","write_trees"]],"maia.transfer":[[18,1,1,"","dist_tree_to_part_tree_all"],[18,1,1,"","dist_tree_to_part_tree_only_labels"],[18,1,1,"","dist_zone_to_part_zones_all"],[18,1,1,"","dist_zone_to_part_zones_only"],[18,1,1,"","part_tree_to_dist_tree_all"],[18,1,1,"","part_tree_to_dist_tree_only_labels"],[18,1,1,"","part_zones_to_dist_zone_all"],[18,1,1,"","part_zones_to_dist_zone_only"]]},objnames:{"0":["py","attribute","Python attribute"],"1":["py","function","Python function"]},objtypes:{"0":"py:attribute","1":"py:function"},terms:{"0":[1,3,4,8,10,14,15,16,17,18],"00":7,"03":10,"1":[1,7,8,10,14,16],"10":[8,14,16,17],"101":10,"11":[9,14],"12":[8,9,10],"13":9,"14":[7,9,14],"15":7,"170":14,"18":8,"180":[10,14],"19":7,"1e":14,"1to1":14,"2":[1,8,10,14,16],"20":14,"2021":10,"21":14,"22":10,"24":8,"25":14,"2d":[1,10,14,16],"3":[1,7,8,10,14],"30":9,"375":16,"3d":[1,14,16],"4":[1,3,5,7,8,10,14],"5":[10,14,16],"50":9,"6":[7,8,10,14],"60":9,"625":16,"7":10,"8":[7,10],"9":[8,10],"case":[4,8,9,10,14,16,17],"cassiop\u00e9":7,"class":3,"default":[3,7,14,15,16,17],"do":[3,7,8,9,10,17,18,19],"export":10,"final":[1,9,10],"float":[14,16],"function":[1,3,4,8,10,14,15,16,17,18,19],"import":[1,3,9,10,14,16,17,18],"int":16,"long":[3,8],"new":[3,8,12,14,19],"public":10,"return":[14,16,17,19],"short":8,"true":[1,14,16,17],"try":[3,4,7,8,14],"var":4,"while":3,A:[1,3,4,7,8,11,14,19],As:[1,8],Be:[10,17],But:3,By:[3,8,10],For:[1,3,4,7,8,9,14,16,17,18],If:[1,3,7,8,9,10,14,16,17],In:[1,7,8,9,10,14,16,17,18],It:[3,5,6,7,8,10,18],Its:8,No:[9,15],On:[7,8],One:1,Such:[8,9],The:[1,3,4,5,7,8,9,10,11,14,15,16,17,18],Their:8,Then:[7,8],There:5,These:[8,10,14,18],To:7,With:[1,8],_:1,_all:18,_exchange_field:14,_gn:8,_initvar:10,_onli:18,_scale:10,_to:8,aa_install_py3:10,abbrevi:4,abil:9,abl:[7,9],abort:15,about:[8,12],abov:[8,9,10,14],absenc:9,absolut:[8,9],accept:[3,14,17],access:[7,8,14,19],accord:[14,18],accordingli:[1,3,14],account:1,accur:9,achiev:17,across:[8,16,18],action:9,actual:[8,14],ad:[3,7,14,17],adapt:8,add:[1,9,14],add_child:10,add_logg:3,add_printer_to_logg:3,addit:[7,8,10,14,17,18],addition:9,address:8,adf:10,admiss:[8,14,16],admit:14,advanc:10,advic:14,advis:7,affect:[8,9,16],affero:9,affin:14,after:[1,3,9,10],against:9,agre:9,agreement:9,akin:1,algo:[1,10,19],algorithm:[2,6,8,10,19],all:[7,8,9,10,14,15,16,17,18],all_to_al:1,alleg:9,alloc:1,allow:[8,9,10,14,15,18,19],alon:9,alreadi:8,also:[1,7,8,9,10,14,15,17,18],alter:9,altern:16,alwai:[1,8,14],among:8,an:[1,3,4,6,7,8,9,10,14,15,16],analysi:11,angl:14,angular:[10,14],ani:[3,8,9,14,18],annot:8,anoth:[7,8,19],anyon:9,anyth:3,anytim:3,api:[6,10,18],apparatu:9,appear:[1,8,14],appli:[1,8,9,14,19],applic:[3,10,15,19],apply_to_field:14,approxim:1,ar:[1,3,6,7,8,9,10,14,15,16,17,18,19],arang:14,arbitrari:8,architectur:8,argument:[3,14,16],aris:3,arithmet:14,arrai:[1,4,8,10,14,16],artifact:[5,7],ascend:[8,14],ask:10,assert:[9,14,16,18],associ:[1,3,6,8,16],assum:[8,9,18],atb:10,atb_extract:10,attach:[3,9],attempt:9,author:9,auto:14,automat:[9,15,16,17],avail:[9,14,15,16,19],averag:14,avoid:15,awar:[3,10,17],ax:14,b:[7,8,14],back:[1,8,9,10],balanc:[1,8,14,16],base:[1,8,10,14,16],basea:14,baseb:14,basenam:14,basi:9,basic:[8,10,14,16],bc:[10,14,17],bc_name:[10,14],bc_t:[10,14],bcdata_t:10,bcdataset:[14,16,18],bcdataset_t:[8,10,14,18],becaus:[3,7,8],becom:[8,9,14],been:[7,9,14,16,18],beetween:8,befor:8,begin:[1,3,14],behalf:9,behaviour:[14,15],being:[8,10],believ:9,belong:[8,14],below:[1,17],benefici:9,best:8,better:14,between:[1,8,14,18,19],bin:10,binari:10,blk1:14,blk2:14,block:[8,10,14,16],blue:8,bnd2:18,board:10,bool:[14,16,17],both:[3,14,16,19],bound:8,boundari:[1,10,14,16],breath:7,bring:[9,14],brought:9,bucket:1,build:[3,5,10],build_dir:7,bump_45:10,burden:3,busi:9,c:[1,3,4,6,8,9,14],cacheblock:16,cacheblocking2:16,call:[1,5,8,10,14,15,17,19],can:[1,3,4,5,6,7,8,9,10,14,15,16,17],cartesian:[14,16],cassiope:[10,11,17],caus:9,cconvert:10,ccx:14,ccy:14,ccz:14,cd:[5,7],cdot:[1,14],cell:[1,4,8,14,16],cell_cent:14,cell_fac:4,cell_renum_method:16,cell_vtx:4,cellcent:14,center:14,centers_to_nod:14,centertonod:14,centos8:10,certainli:3,cfd5_21:10,cfd:[8,11,19],cgn:[1,4,6,7,10,14,16,17,18],cgns_elmt_nam:16,cgns_io_tre:17,cgnsbase_t:[1,8,10,16],cgnscheck:10,cgnsconvert:10,cgnsname:16,cgnsnode:16,cgnstree:[14,16,17,18],cgnstree_t:1,chain:[10,19],chang:[12,15,19],charact:9,characterist:8,charg:9,check:[8,10],choic:[9,14],choos:[3,9,14],choosen:14,chosen:14,ci:7,circular:14,circumst:9,claim:9,clang:7,cleaner:7,clear:9,clone:7,close:8,closest:14,closestpoint:14,cloud:14,cluster:[6,10],cmake:7,code:[3,4],coher:[4,7,8],collect:[1,8],color:[8,10],com:7,combin:9,come:[8,9,14],comfort:10,comm:[1,10,14,16,17,18],comm_world:[1,3,10,14,15,16,17,18],commerci:9,common:9,commonli:10,commun:[1,3,14,15,16,17,18],comparison:1,compat:[5,10],compil:[7,10,17],complet:[8,9],complianc:9,compliant:[8,9,10],compon:14,comput:[8,9,10,11,14,16,17,19],compute_balanced_weight:16,compute_cell_cent:14,compute_edge_cent:14,compute_face_cent:14,compute_nosplit_weight:16,compute_regular_weight:16,compute_wall_dist:[10,14],computegrad:10,concaten:1,concatenate_jn:14,concept:10,concern:9,condit:16,conf:[3,15],configur:[5,7],confirm:8,conflict:9,conform:14,conformize_jn_pair:14,connect:[1,4,8,10,16],connex:8,consecut:1,consequ:16,consequenti:9,consid:[8,14,18],consist:[1,10,11,19],consol:3,constant:[1,8],constitut:9,constraint:[8,14],constru:9,construct:6,consult:6,contain:[1,7,8,9,10,12,14,16,17,18],container_nam:14,containers_nam:[10,14],content:9,context:8,contigu:8,contract:9,contrari:8,contrast:8,contribut:[6,9,14],contributor:9,control:[9,14,16,17],convei:9,conveni:[3,16,18],convent:[1,2,3,10,14],convers:19,convert:[10,17],convert_elements_to_mix:14,convert_elements_to_ngon:[1,14],convert_mixed_to_el:14,convert_s_to_ngon:[10,14],convert_s_to_u:14,coordin:[8,14,16],coordinatei:[8,17],coordinatex:[8,10],coordinatez:17,copi:[9,14,16],copyright:9,correct:[3,5,9,14],correspond:[8,14,16,18,19],cost:[1,9],could:[1,8,19],count:[1,8],counter:9,court:9,cover:9,cpost:10,cpp:7,cpp_cgn:[5,7],cpp_cgns_unit_test:5,crazi:16,creat:[1,7,8,9,10,14,15,16,17,18],create_extractor_from_zsr:14,create_interpolator_from_part_tre:14,creation:9,cross:[9,14],cross_domain:14,ctest:5,ctransform:10,current:[8,16],curv:10,custom:16,cut:8,cuthil:16,cz:14,d:14,dai:9,daili:10,damag:9,data:[6,10,11,16,17,18,19],dataarrai:17,dataarray_t:[10,18],databas:10,datai:18,datax:18,dataz:18,dcmake_install_prefix:7,deadlock:15,deal:[8,9,17],death:9,debug:[16,17],decid:3,declar:[3,8],declaratori:9,decreas:14,dedic:10,def:3,defect:9,defend:9,defin:[6,8,9,10,14,16,18],definit:[8,18],delet:9,densiti:18,depend:[1,8,10,14],describ:[1,4,5,6,8,9,10,14,16,19],descript:[2,9,15,16],desir:9,destin:19,destructur:[6,14],detail:[0,6,7,9,15],detain:8,detect:[10,14],determin:1,develop:[6,11],developp:[3,7],dict:[14,16],dictionari:[16,18],dictionnari:16,did:8,differ:[7,8,9,10,16,19],dimens:[8,14,16],dimension:16,dir:7,direct:[9,16],directli:[8,9,14,15,19],directori:9,dirichletdata:18,disabl:[14,15],disable_mpi_excepthook:15,discretedata:16,discretedata_t:[14,18],disk:19,displai:9,displaystyl:1,dispos:8,dist:[1,8,10,14,19],dist_tre:[1,10,14,16,17,18],dist_tree_:14,dist_tree_bck:16,dist_tree_elt:1,dist_tree_gath:14,dist_tree_ini:14,dist_tree_src:14,dist_tree_tgt:14,dist_tree_to_fil:[10,17],dist_tree_to_part_tree_al:18,dist_tree_to_part_tree_only_label:18,dist_tree_u:14,dist_zon:[1,18],dist_zone_to_part_zones_al:18,dist_zone_to_part_zones_onli:18,distanc:[14,19],distinct:14,distinguis:1,distinguish:[8,9],distribut:[1,6,10,16,18,19],distribute_tre:16,distributor:9,disttre:[14,18],disttree_:14,dive:8,divid:[1,16,17],divis:8,doabl:3,doctest:7,doctrin:9,document:[7,9,12,13,14,16,17],doe:[3,7,8,9,14,16],domain:[8,14],domin:1,don:7,done:[1,7,14],download:[7,10],doxygen:7,dr:5,drafter:9,driver:17,dtype:14,due:8,dump:16,dump_pdm_output:16,duplic:[10,14],duplicate_from_periodic_jn:10,duplicate_from_rotation_jns_to_360:14,dure:[1,5,7],dynam:[7,11,15],e:[1,5,7,8,14,15,18,19],each:[1,3,4,8,9,10,12,14,16,17,18,19],earlier:9,eas:5,easiest:[15,17],easili:10,edg:[4,8,14,16],edge_cent:14,edge_length:16,edge_vtx:4,effect:[15,16],effici:10,either:[4,9,14,19],el8:10,el8_mpi:10,element:[1,4,8,10,14,16],element_t:[8,14],elementari:10,elementconnect:[1,8],elementrang:[8,14],elements_t:[8,14,16],elements_to_ngon:0,elementstartoffset:[1,10],elementtyp:14,elev:14,els:16,elsa:[10,11],elt:[4,14],elt_fac:4,elt_typ:14,elt_vtx:4,empti:[8,10,14,16],enclos:14,encompass:3,encount:8,end:[1,8,9,18],enforc:9,enforce_ngon_pe_loc:14,englob:14,enough:3,enrich:19,ensur:14,entir:[8,9],entiti:[8,9,14,16],environ:[3,5,6,7,10,15],eq1:1,equal:[1,8,14,16],equat:[1,14],equilibr:16,equival:[3,8,9],error:[3,15],essenti:9,etc:[7,8,16],even:[9,19],event:9,everi:[8,9,16,18],everyth:18,exactli:[1,14],exampl:[4,8,10,14,16,17,18,19],except:[1,9,14,18],excepthook:15,exchang:[1,8,14,18],exchange_field:14,exclud:[7,9,14,18],exclude_dict:18,exclus:9,execut:[1,5,8,15],exercis:9,exist:[3,8,14,16,18,19],expect:[4,10,14,16,18],expected_:4,experiment:[1,14],explain:[8,15],explicitli:[8,9,10,14],exploit:9,express:[9,16],extend:16,extens:[3,6,11],extent:9,exterior:1,extern:[1,5,7,14],extract:[6,10],extract_part_from_bc_nam:14,extract_part_from_zsr:[10,14],extract_tre:10,extract_tree_dist:10,extracted_bc:14,extracted_tre:14,extractor:14,f:[1,10,14],face:[4,6,8,14,16],face_cel:4,face_cent:14,face_renum_method:16,face_vtx:4,facecent:[1,14,16],facevtx:1,fact:8,factori:[10,14,17,18,19],factual:9,fail:[9,14],failur:9,fake:14,fals:[14,16,17],famili:[10,14],family_nam:14,family_t:14,familynam:14,familyname_t:14,faster:1,featur:14,fee:9,feel:3,few:[7,14,19],field:[8,10,14,16,19],fifti:9,figur:10,file:[5,7,8,9,10,15,19],file_print:[3,15],file_to_dist_tre:[1,10,14,16,17,18],filenam:[17,18],fill:[8,17],fill_size_tre:17,filter:[7,17,18],find:[1,14],find_closest_point:14,fine:8,finer:17,first:[1,6,8,9,12,13,14],firstsolut:18,fit:9,flat:[10,14],flowsolut:[14,16,18],flowsolution_t:[8,10,14,18],fluid:11,folder:[3,5],follow:[1,7,8,9,10,14,15,16,17,18],footnot:8,forc:15,form:8,formal:8,format:10,former:10,found:[7,10,14,17],foundat:9,fr:11,fraction:16,framework:[10,11],free:9,friendli:10,from:[1,3,7,8,9,10,14,17,18,19],fsol:14,full:[8,14,15,16,17],full_onera_compat:14,fulli:[8,10],further:9,g:[1,5,7,8,15,18,19],gather:[1,8,14,17],gc:14,gc_t:[8,14],gcc8:10,gcc:[7,10],gener:[5,6,9,11,15,17,19],generate_dist_block:[10,14,16,17],generate_jns_vertex_list:14,geometr:[8,10,16,18],get:[1,5,7,8,16],get_all_zone_t:[10,14,16,18],get_child_from_label:10,get_child_from_nam:14,get_nam:14,get_node_from_label:14,get_node_from_nam:14,get_node_from_name_and_label:10,get_node_from_path:18,get_nodes_from_nam:14,get_rank:[3,14,16,17],get_siz:16,getnodefromtyp:16,git:[5,7],git_config_submodul:[5,7],github:[7,11],gitlab:[6,7,10],give:8,given:[8,9,10,14,16],global:[1,3,8,15,18],globalnumb:[8,16,17],gnu:9,gnum:14,go:10,goe:8,good:8,goodwil:9,govern:9,gradient:10,grai:10,graph:16,graph_part_tool:[14,16],green:8,grid:[14,16],gridconnect:14,gridconnectivity1to1_t:14,gridconnectivity_0_to_1:8,gridconnectivity_t:14,gridcoordin:[14,16],gridcoordinate_t:14,gridcoordinates_t:8,gridloc:[1,14,16],gridlocation_t:10,group:10,guarante:14,guid:17,h5py:17,ha:[7,8,9,12,14,17],hand:16,handl:3,happen:8,hard:4,have:[1,7,8,9,10,14,15,16,18],have_isolated_fac:14,hdf5:[7,8,17],hdf:[10,17],he:3,heavi:8,heavyweight:1,held:9,help:10,helper:7,helpful:11,here:[1,5,7,10],herebi:9,hereof:9,hesit:10,heterogen:[8,16],hex:1,hexa:[1,8,14],hexa_8:[4,16],high:[6,10,11,17,18],higher:14,hilbert:[14,16],hold:[4,8,16],homogen:16,host:6,how:[5,8,9,15,17],howev:[7,8,9,16,18],hpc:16,hpp:3,html:7,http:[7,9,11],hybrid:16,hyperplan:16,i:[1,8,9,14,16],i_rank:[14,16],id:[1,8,14,17],ident:1,idw:14,idw_pow:14,ie:[14,16],ii:9,illustr:[8,10,17],imagin:8,impi21:10,impi:10,implement:[8,14,16,19],impli:[1,3,9],implicit:[8,10],imposs:9,inaccuraci:9,incident:9,includ:[1,3,8,9,10,14,18],include_dict:18,increas:14,incur:9,indemn:9,indemnifi:9,independ:[8,14,17],index:[4,8],indic:[8,14],indirect:9,indirectli:9,indistinctli:14,individu:9,induc:8,info:[3,7,15],inform:[1,8,9,12,15,16,17],informat:7,infra:7,infring:9,init:[5,7,14],initi:[9,14,16],injuri:9,inplac:[14,18,19],input:[1,10,14,16,19],insert:[1,17],insid:8,instal:[10,16,17],install_dir:7,instanc:[3,7,8],instanci:[3,14],instead:7,instruct:15,insur:10,int32:14,integ:[8,10,16],intel:[7,10],intellectu:9,intend:9,interfac:[14,16],interlac:14,intern:[1,8,14],interpolate_from_part_tre:14,interpret:3,interv:[4,8],introduc:8,introduct:[6,10],intuit:8,invers:[14,17],invert:1,investig:1,involv:[1,10,14],io:[1,6,10,11,14,16,18],irrelev:16,irrespect:14,is_same_tre:16,iso_field:14,iso_part_tre:14,iso_surfac:14,iso_v:14,isobarycent:14,isol:14,isosurf:14,isosurf_tre:14,isosurfac:14,issu:[6,10],iter_all_zone_t:14,ith:8,its:[1,3,7,8,9,10,14,16],itself:[3,8,17],j:[7,8],jcoulet:10,jn:14,jn_path:14,jn_paths_for_dupl:14,job:10,join:[14,18],joint:[1,5],jth:8,judgment:9,judici:9,jurisdict:9,just:[1,7,8],k:1,keep:[1,8,14,16,17],kei:[8,18],kept:1,keyword:16,kind:[1,6,7,8,9,14,16,18,19],know:8,knowledg:8,known:[9,16,18],kwarg:[14,16],label:[1,14,18],languag:9,larg:[10,16],last:7,lastli:18,later:9,latter:3,law:9,ld_library_path:[5,7,10],lead:16,least:16,left:[1,16],legaci:[8,10,14,17],legal:9,len:[14,16],length:[8,16],less:[8,16],lesser:9,let:[1,8,17],level:[5,6,8,10,11,14,16,17],lexicograph:[1,16],liabl:9,lib:10,librari:[3,4,6,7,8,10,11,16,17],light:15,lightweight:8,like:[3,7,8,9,14,15],limit:[8,14],line:[7,10],link:[8,17],list:[14,16,17,18],lista:14,listb:14,listen:3,ln:8,ln_to_gn:8,load:[8,10,16,17,19],load_collective_size_tre:17,loc:14,loc_contain:14,loc_toler:14,local:[1,3,4,8,14],localize_point:14,locat:[7,8,9,10,14,16],locationandclosest:14,log:[1,2],logger:15,logging_conf_fil:[3,15],logo:9,look:[1,3,8,9,10,15],loss:9,lost:9,low:[8,11,17],lower:[1,14],lowest:14,m:18,machin:7,made:[6,9,14],magic:18,mai:[7,8,9,17,18],maia:[1,5,7,10,12,14,15,16,17,18,19],maia_build_fold:7,maia_doctest_unit_test:5,maia_fold:[5,7],maia_hom:10,maia_poly_new_to_old:10,maia_poly_old_to_new:10,main:[6,17,19],maintain:9,make:[7,8,9,14],malfunct:9,manag:[2,6,7,8,9,15,16,18],mandatori:14,mani:16,manner:[9,17],manual:[6,10],manuel:19,map:[8,10,18],mark:9,match:14,matcha:10,matchb:10,materi:[9,10],mathcal:1,mathtt:8,matrix:14,matter:9,maximum:9,me:10,mean:[1,4,7,8,9,10,14,16,18],memori:[1,8,15],merchant:9,merg:[10,14],merge_all_zones_from_famili:14,merge_connected_zon:[10,14],merge_zon:14,merge_zones_from_famili:14,merged_zon:14,mergedzon:14,mesh:[1,4,11,16,18],mesh_dir:[1,14,16],mesh_fil:10,messag:3,metadata:14,method:[3,8,9,14,16,18,19],mid:11,minim:[10,16],miss:[16,18],mix:[1,8,10,14],mlog:3,modif:9,modifi:[1,14,19],modul:[7,10,11,17,19],momentum:18,momentumi:18,momentumx:18,momentumz:18,more:[3,6,8,9,14,15,18],moreov:9,most:[3,6,8,10],move:14,move_field:14,mpart:16,mpi4pi:[1,7,10,14,16,17,18],mpi:[1,3,5,7,10,14,15,16,17,18],mpi_abort:15,mpi_comm:1,mpi_file_print:3,mpi_rank_0_stderr_print:[3,15],mpi_rank_0_stdout_print:[3,15],mpi_stderr_print:3,mpi_stdout_print:[3,15],mpicomm:[14,16,17,18],mpirun:5,mpl:9,msg:3,much:[1,8,10],multipl:[10,14],must:[7,8,9,10,14,16,18],my:3,my_app:3,my_fil:3,my_logg:3,my_print:3,my_them:3,mybas:8,mynod:8,myzon:8,n0:8,n1:10,n1xn2:10,n2:10,n:[1,8,14],n_:1,n_cell:14,n_cell_in_sect:1,n_cell_per_cach:16,n_face_of_cell_typ:1,n_face_per_pack:16,n_i:1,n_part:16,n_part_tot:16,n_proc:4,n_rank:[8,16],n_thing:4,n_vtx:[4,16],naca0012:14,name:[3,7,9,10,14,16,17],nan:14,natur:19,ncell:14,nearest:14,nearli:16,necessari:9,necessarili:8,need:[1,3,7,8,14,16,18],neglig:9,net:7,never:8,new_child:14,new_dataarrai:14,new_flowsolut:14,new_zonesubregion:[10,14],next:[8,10,18],nface:[8,14,16],nface_n:[4,10,14,16],nface_to_p:14,nfaceel:14,nfacenod:14,ngon:[8,10,14,16],ngon_n:[10,14,16],ngonel:16,ngons_to_el:14,nodal:[14,16],node:[1,8,10,14,16,17,18],non:[8,9,10,16],none:[3,10,14,16,18],norm:8,normal:1,notabl:[5,8],notat:11,note:[1,7,8,9,10,14,15],noth:9,notic:8,notifi:9,notwithstand:9,now:[1,3,8,10,14,17],np:[5,14],number:[1,4,5,9,14,16],numpi:14,ny:8,o:1,object:[3,8,14],oblig:9,observ:10,obtain:[9,16],occur:[1,14,15],odd:14,off:1,offer:9,often:[5,7,8,10,19],old:[10,14],onc:[1,7,10,14],one:[1,7,8,9,10,14,17,18],oneapi:10,onera:[6,7,11,14],onera_spack_repo:7,ones:[1,5,7,8,14],ongo:9,onli:[1,4,8,9,14,16,17,18,19],only_uniform:16,open:[4,6,8],oper:[1,10,14,16,17,18,19],opposit:[10,14],opposite_jn:10,opt:10,option:[8,9,14,18],order:[1,5,6,8,9,10,14,16],ordinari:9,org:9,organ:5,orient:[3,16],origin:[1,8,9,14,16],os:18,other:[5,8,9,10,14,16],otherwis:[9,16,17],our:8,out:[7,10],out_fs_nam:14,outlin:10,output:[3,14,15,16,17],output_path:14,outstand:9,over:[1,6,8,14,16],overrid:[7,15],overview:6,own:[7,8,9,10,15,19],owner:16,ownership:9,p0:8,p1:8,p2:8,packag:[7,10],page:[6,12,15],pair:14,paradigm:[7,8,16],paradigma:14,parallel:[6,10,14,16,17,19],parallel_sort:1,paramet:[14,16,17,18],parameter:5,parametr:14,parent:[1,4,10,14,16],parentel:[1,4,14],parentelementsposit:1,parentposit:1,parmeti:[14,16],part:[6,8,9,10,14,16,19],part_interface_loc:16,part_tre:[10,14,16,17,18],part_tree_iso:14,part_tree_src:14,part_tree_tgt:14,part_tree_to_dist_tree_al:[10,18],part_tree_to_dist_tree_only_label:18,part_tree_to_fil:17,part_zon:[10,14,18],part_zones_to_dist_zone_al:18,part_zones_to_dist_zone_onli:18,parti:[7,9],partial:[1,8],particular:[1,3,7,9],partion:14,partit:[1,6,10,18,19],partition_dist_tre:[10,14,16,17,18],patch:18,patent:9,path:[7,10,14,16,17,18],pattern:1,pe:16,pe_to_nfac:[10,14],peak:1,penalti:8,penta_6:16,per:[8,16],percent:9,perfect:[1,6],perform:[9,10,14,16,17],period:14,permit:9,person:9,physic:16,pick:18,pictur:10,piec:8,pip3:7,place:[1,9,14],plane:[10,14],plane_eq:14,plane_slic:[10,14],planeslic:10,point:[5,8,14],point_cloud:[10,14],point_list:14,pointlist:[1,8,14],pointlistdonor:14,pointrang:8,pointrange_t:10,poli:[14,16,17],polici:14,poly_new_to_old:14,poly_old_to_new:14,polyedr:14,polygon:14,polyhedr:[10,14],poor:16,popul:7,portabl:11,portion:9,posit:[1,8],possibl:[8,9,10,16,18],post:[10,11],power:[9,14],practic:[1,7,10],pre:[1,10,11,16],prefer:[9,10],prefix:3,present:[7,8,14],preserv:[8,10,14],preserve_orient:[14,16],pressur:18,prevent:9,previou:[1,8],previous:1,princip:9,print:3,printer:15,prior:9,prism:[1,14],privileg:17,proc:[4,8,14,16],process:[1,4,5,7,8,9,10,11,14,15,16,17],produc:[8,14,16,17,19],product:10,profit:9,program:3,progress:11,prohibit:9,project:14,project_build_dir:5,project_src_dir:5,project_util:7,propag:14,properli:8,properti:[1,8,9],prove:9,provid:[0,3,6,7,8,9,10,11,14,15,16,17,18,19],provis:9,provision:9,pt:[10,14,16,18],ptscotch:[14,16],publish:9,pure:3,purg:10,purpl:8,purpos:[9,15,17],put:[3,8,9,10],px:8,pybind11:7,pycgn:10,pyra:[1,14],pyra_5:[14,16],pytest:[5,7],pytest_check:7,pytest_parallel:7,python3:[7,10],python:[3,4,6,7,8,10,11,15,17],pythonpath:[5,7,10],pytre:[10,14,16,17,18],quad:[1,8,14],quad_4:[8,14,16],quad_4_interior:1,qualiti:9,quantiti:8,quarter_crown_square_8:18,quick:6,quickselect:1,quit:7,r:14,rais:15,rand:14,random:[14,16],rang:[7,14,16],rank:[1,8,15,16,17,18],rank_id:16,rather:3,raw:16,read:[3,10,14,17],read_tre:[16,17],readi:[7,10],rearang:14,rearrange_element_sect:14,reason:[8,9],receipt:9,receiv:[1,3,9,10,14,16],recip:7,recipi:9,recommend:10,reconstitut:14,reconstruct:8,record:14,recover_dist_tre:[10,16],red:8,redistribut:14,redistribute_tre:14,reduc:14,reduct:14,redund:8,refer:[3,8,9,14,16],referencestate_t:14,reform:9,regener:16,regular:[1,8,14],reinstat:9,relat:[8,9,14,17,18],relax:8,releas:13,relev:[9,14,16,18],reli:[10,11],remain:14,remedi:9,remot:1,remov:[7,9,14,17],remove_p:14,removenfac:14,removep:14,renam:[9,14],renumb:[8,16],repair:9,repart:14,repartit:8,replac:[1,14],replic:8,repo:7,report:[10,14,16],repositori:[5,6,7],repres:[8,9,10],reproduc:9,request:[14,16,17],requir:[1,7,8,9,14,18],resel:9,reshap:14,resp:[14,18],respect:[3,8,9,10],restrict:[9,14],result:[1,8,9,14,19],retriev:[6,11,14],revers:16,right:[1,9,16],risk:9,rk:3,rm_nodes_from_nam:17,room:10,root:14,rotat:14,rotation_angl:14,rotation_cent:14,round:8,royalti:9,ruamel:7,rule:18,run:[5,10],s:[6,7,8,9,10,14,16,17,18],s_twoblock:[10,14,16],said:8,sale:9,same:[1,7,8,10,14,16],sampl:[10,17],sample_mesh_dir:18,sator:10,save:[10,19],scalar:16,scale:1,scatter:1,scenario:8,scientif:11,scratchm:10,script:[3,10],sdtout:15,search:14,second:[8,14],secondsolut:18,section:[0,8,9,10,14,18],see:[1,7,8,9,10,14,15,16,17],seen:8,select:[3,14],self:3,sell:9,semi:[4,8],send:[1,10],sens:8,separ:[9,17],seq:14,sequenc:14,sequenti:[1,10,17],servic:[9,11],set:[3,10,11,14,15,16],set_nam:10,setup:10,sever:[3,7,8,10],sh:[5,7,10],shall:9,shape:[8,10,14,17],share:[8,9,14],shift:[1,14],shortcut:14,should:[1,3,7,9,10,16,17],show:17,sid:[10,14,17],side:14,similar:[1,14,18],similarli:14,simpl:10,simpli:[8,16],sin:10,sinc:[1,8,10],singl:[8,10,14,15,16,17],single_fil:17,singular:4,site:10,size:[1,4,8,10,16,17],size_tre:17,skeleton:8,skill:9,slice:[10,14],slice_tre:[10,14],small:[16,19],snake_cas:4,so:[3,4,7,8,9],softwar:[6,9,11],solut:14,solver:[8,19],some:[0,6,7,8,9,10,14,15,17,19],someth:[1,8],sometim:8,sonic:10,sort:[1,8],sort_by_rank:1,sort_int_ext:16,sourc:[5,6,10,14,18,19],spack_root:7,spacki:7,span:8,spane:8,special:[9,14],specialsolut:18,specif:[9,16],specifi:[14,18],speedup:1,sphere:14,sphere_eq:14,spheric:14,spherical_slic:14,sphinx:7,spiro:10,spiro_el8:10,split:[1,8,10,16],splitabl:8,spot:1,squar:8,src_dir:7,src_sol:14,src_tree:14,stable_sort:[1,14],stage:1,standalon:10,standard:[1,6,8,10,11,14,16],start:[3,6,8,14,18],start_rang:14,stat:[3,15],statutori:9,stck:10,std:7,std_e:[1,3,5,7],std_e_unit_test:5,stderr:[3,15],stderr_print:3,stdout:3,stdout_print:3,step:[1,7,17],steward:9,still:[3,14],stoppag:9,storag:11,store:[1,8,14,17],str:[14,16,17,18],strategi:14,stride:8,string:3,structur:[4,8,10,14,16,18,19],sub:[8,15],subfil:17,subject:9,sublicens:9,submesh:[8,14],submodul:5,subregion:[8,16],subset:[8,14],subset_loc:14,subset_merg:14,substanc:9,suffici:9,suffix:[4,14],suit:8,sum:[1,8,16],sum_:1,summar:15,support:[9,14,16,17,18],suppos:[14,18],sure:7,surfac:[10,14],surviv:9,sy:15,system:[7,11],sz:4,t:[7,14],t_:1,t_p:1,tabl:15,take:[1,8,14,18],target:[14,18],tata:18,tell:7,term:8,termin:15,test:[3,4,10],test_util:[1,14,16,18],tet:[1,14],tetra:1,tetra_4:[10,16],text:9,textrm:1,tgt_sol:14,tgt_tree:14,tgt_zone:14,than:[1,3,8,9,14],thank:[1,8,10,14],thei:[1,3,7,8,9,10,14,17,18,19],them:[1,3,7,8,14],theme:3,theoret:1,theori:9,therefor:8,thereof:9,theses:10,thi:[0,1,3,4,5,6,7,8,9,10,12,14,15,16,17,18,19],thing:4,third:[7,9],those:[1,8,9,14,16,18],thought:14,three:[1,16,19],through:[8,14,15,16,19],thu:[8,19],tild:14,time:[1,3,8,9,15],tl:5,tmp:7,tmp_user:10,toler:[10,14],too:[8,16],tool:[10,14,16,19],topolog:[10,16],tort:9,total:[8,16],total_s:8,track:[6,8],trade:1,trademark:9,transfer:[8,9,10,14,16,19],transfer_dataset:14,transform:[6,10],transform_affin:14,translat:14,transmit:16,transpar:7,treat:8,treatement:14,tree:[1,6,10,14,16,19],tree_:10,tree_u:10,tri:[1,14],tri_3:[14,16],tri_3_interior:1,tune:14,turbulentdist:14,tutuz:18,twice:[1,3],two:[1,4,8,14,17,18,19],type:[1,3,8,14],typic:[3,7,8,19],u:[14,16],u_atb_45:[10,14],u_naca0012_multizon:14,u_twoblock:10,uelt_m6w:[1,14],unabl:10,uncatch:15,unchang:14,under:[1,6,9,10,14],underli:10,understand:9,undesir:15,unenforc:9,unfil:8,unfortun:4,uniform:[14,16],union:8,uniqu:[1,14,17],unit:4,unless:9,unloc:14,unmodifi:9,unpartit:8,unsign:14,unstructur:[1,8,10,14,16,19],unstuctur:16,until:[3,8,9,14],unwant:17,up:[1,3,10,14],updat:[5,7,14,19],us:[1,3,4,5,6,7,8,10,14,15,16,17,18,19],usag:[10,15],user:[3,6,7,9,10,15,16],usual:4,util:[1,3,7,10,14,16,18],v3:7,v5:10,v:[9,14],valid:14,validli:9,valu:[8,14,16],vari:1,variabl:[3,4,8,15],variant:1,variou:[6,14],vector:[14,16],vectori:14,verbos:5,veri:1,versa:3,version:[7,8,10,12],vertex:[4,8,10,14,16],vertic:[1,4,8,14,16],vice:3,view:[1,7,8],virtual:8,vol_rank:14,volum:[10,14],vtx:[4,14],vtx_renum_method:16,vtx_sol:14,wa:[1,8,9,16],wai:[7,8,14,15,17],wall:[10,14,19],walldist:[10,14],want:[3,5,6,7,8,9,10,17],warn:[3,14,15],we:[1,3,4,5,7,8,10,14,17,18],weight:[14,16],well:[8,10,16,17],were:[1,8,14],what:[3,8,12,17],when:[3,4,8,15,16,18],where:[1,3,7,8,9,14,16],whether:9,which:[1,5,8,9,10,11,14,15,16,17,18,19],white:10,who:[3,9,14],whose:[8,14,19],why:10,wide:9,wildcard:18,within:[8,9,14,16,17],without:[7,9,16],work:[7,10],workflow:[2,10,17,19],world:[9,10],wors:1,worst:8,worth:3,would:[1,3,8,9],writ:8,write:11,write_tre:17,written:[4,8,17],x:[8,10,14],x_0:14,y:[8,14],y_0:14,yaml:[1,7,14,16,18],yet:[8,14,16],ym:17,you:[6,7,8,9,10,19],your:[7,9,10,15,17,19],z:14,z_0:14,zero:16,zm:17,zone0:8,zone1:8,zone:[1,8,14,16,17],zone_path:14,zone_t:[8,14,16,18],zone_to_part:16,zonebc:[16,18],zonegridconnect:[10,16],zonenam:14,zonesubregion:[14,16,18],zonesubregion_t:[8,14,18],zsr:10,zsr_name:14,zsrwall:10},titles:["Algorithms description","elements_to_ngons","Developer Manual","Log management","Conventions","Development workflow","Welcome to Maia!","Installation","Introduction","License","Quick start","Related projects","Release notes","v0.1.0 (2021)","Algo module","Configuration","Factory module","File management","Transfer module","User Manual"],titleterms:{"0":[9,12,13],"1":[9,12,13],"10":9,"2":9,"2021":[12,13],"3":9,"4":9,"5":9,"6":9,"7":9,"8":9,"9":9,"cassiop\u00e9":11,"new":9,"public":9,A:9,With:9,addit:9,algo:14,algorithm:[0,1,14],all:1,altern:1,applic:[8,9],argument:1,avail:3,b:9,build:7,calcul:14,cellfac:1,cgn:[8,11],code:9,complex:1,compli:9,comput:1,concept:8,condit:9,configur:[3,15],connect:14,convent:[4,8],convers:14,core:8,creat:3,data:[8,14],date:9,definit:9,depend:7,descript:[0,1],design:1,develop:[2,5,7],disclaim:9,distribut:[8,9,14,17],divid:8,document:6,due:9,effect:9,elements_to_ngon:1,environn:10,exampl:1,except:15,execut:9,exhibit:9,explan:1,extract:14,face:1,factori:16,fair:9,featur:[12,13],field:18,file:[3,17],folder:7,form:9,from:16,gener:[1,14,16],geometr:14,geometri:14,grant:9,handl:15,highlight:10,inabl:9,incompat:9,instal:7,interior:1,interpol:14,introduct:8,io:17,larger:9,launch:5,level:18,liabil:9,licens:9,limit:9,litig:9,log:[3,15],logger:3,maia:[3,6,8],manag:[3,14,17],manual:[2,7,19],mesh:[8,10,14],miscellan:9,modifi:9,modul:[5,14,16,18],mozilla:9,mpi:8,name:[4,8],nface:1,ngon:1,note:12,notic:9,number:8,option:[7,16],other:[4,7],output:1,overview:8,own:3,paradigm:11,parallel:[1,8],partit:[8,14,16,17],prefer:7,printer:3,procedur:7,project:11,quick:10,raw:17,recov:16,regul:9,relat:11,releas:12,reorder:16,repartit:16,represent:9,resourc:10,respons:9,scope:9,secondari:9,section:1,sequenti:14,simplifi:1,sourc:[7,9],spack:7,specif:[3,8],start:10,statut:9,sub:5,submodul:7,subsequ:9,summari:6,support:10,term:9,termin:9,test:5,through:7,transfer:18,transform:[1,14],tree:[8,17,18],troubleshout:10,us:9,user:19,v0:[12,13],version:9,warranti:9,welcom:6,work:9,workflow:[5,7],write:17,your:3,zone:18}}) \ No newline at end of file diff --git a/docs/1.0/user_manual/algo.html b/docs/1.0/user_manual/algo.html new file mode 100644 index 00000000..224cc3b7 --- /dev/null +++ b/docs/1.0/user_manual/algo.html @@ -0,0 +1,1459 @@ + + + + + + + + + + Algo module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Algo module

+

The maia.algo module provides various algorithms to be applied to one of the +two kind of trees defined by Maia:

+
    +
  • maia.algo.dist module contains some operations applying on distributed trees

  • +
  • maia.algo.part module contains some operations applying on partitioned trees

  • +
+

In addition, some algorithms can be applied indistinctly to distributed or partitioned trees. +These algorithms are accessible through the maia.algo module.

+

The maia.algo.seq module contains a few sequential utility algorithms.

+
+

Distributed algorithms

+

The following algorithms applies on maia distributed trees.

+
+

Connectivities conversions

+
+
+convert_s_to_u(disttree_s, connectivity, comm, subset_loc={})
+

Performs the destructuration of the input dist_tree.

+

This function copies the GridCoordinate_t and (full) FlowSolution_t nodes, +generate a NGon based connectivity and create a PointList for the following +subset nodes: +BC_t, BCDataSet_t and GridConnectivity1to1_t. +In addition, a PointListDonor node is generated for GridConnectivity_t nodes.

+

Metadata nodes (“FamilyName_t”, “ReferenceState_t”, …) at zone and base level +are also reported on the unstructured tree.

+
+

Note

+

Exists also as convert_s_to_ngon() with connectivity set to +NGON_n and subset_loc set to FaceCenter.

+
+
+
Parameters
+
    +
  • disttree_s (CGNSTree) – Structured tree

  • +
  • connectivity (str) – Type of elements used to describe the connectivity. +Admissible values are "NGON_n" and "HEXA" (not yet implemented).

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • subset_loc (dict, optional) – Expected output GridLocation for the following subset nodes: BC_t, GC_t. +For each label, output location can be a single location value, a list +of locations or None to preserve the input value. Defaults to None.

  • +
+
+
Returns
+

CGNSTree – Unstructured disttree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree_s = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+dist_tree_u = maia.algo.dist.convert_s_to_u(dist_tree_s, 'NGON_n', MPI.COMM_WORLD)
+for zone in maia.pytree.get_all_Zone_t(dist_tree_u):
+  assert maia.pytree.Zone.Type(zone) == "Unstructured"
+
+
+
+ +
+
+convert_elements_to_ngon(dist_tree, comm, stable_sort=False)
+

Transform an element based connectivity into a polyedric (NGon based) +connectivity.

+

Tree is modified in place : standard element are removed from the zones +and the PointList are updated. If stable_sort is True, face based PointList +keep their original values.

+

Requirement : the Element_t nodes appearing in the distributed zones +must be ordered according to their dimension (either increasing or +decreasing).

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by standard elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • stable_sort (bool, optional) – If True, 2D elements described in the +elements section keep their original id. Defaults to False.

  • +
+
+
+

Note that stable_sort is an experimental feature that brings the additional +constraints:

+
+
    +
  • 2D meshes are not supported;

  • +
  • 2D sections must have lower ElementRange than 3D sections.

  • +
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+ngons_to_elements(t, comm)
+

Transform a polyedric (NGon) based connectivity into a standard nodal +connectivity.

+

Tree is modified in place : Polyedric element are removed from the zones +and Pointlist (under the BC_t nodes) are updated.

+

Requirement : polygonal elements are supposed to describe only standard +elements (ie tris, quads, tets, pyras, prisms and hexas)

+

WARNING: this function has not been parallelized yet

+
+
Parameters
+
    +
  • disttree (CGNSTree) – Tree with connectivity described by NGons

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +
+
+convert_elements_to_mixed(dist_tree, comm)
+

Transform an element based connectivity into a mixed connectivity.

+

Tree is modified in place : standard elements are removed from the zones. +Note that the original ordering of elements is preserved.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by standard elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+convert_mixed_to_elements(dist_tree, comm)
+

Transform a mixed connectivity into an element based connectivity.

+

Tree is modified in place : mixed elements are removed from the zones +and the PointList are updated.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by mixed elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD)
+maia.algo.dist.convert_mixed_to_elements(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+rearrange_element_sections(dist_tree, comm)
+

Rearanges Elements_t sections such that for each zone, +sections are ordered in ascending dimensions order +and there is only one section by ElementType. +Sections are renamed based on their ElementType.

+

The tree is modified in place. +The Elements_t nodes are guaranteed to be ordered by ascending ElementRange.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with an element-based connectivity

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_block(11, 'PYRA_5', MPI.COMM_WORLD)
+pyras = PT.get_node_from_name(dist_tree, 'PYRA_5.0')
+assert PT.Element.Range(pyras)[0] == 1 #Until now 3D elements are first
+
+maia.algo.dist.rearrange_element_sections(dist_tree, MPI.COMM_WORLD)
+tris = PT.get_node_from_name(dist_tree, 'TRI_3') #Now 2D elements are first
+assert PT.Element.Range(tris)[0] == 1
+
+
+
+ +
+
+generate_jns_vertex_list(dist_tree, comm, have_isolated_faces=False)
+

For each 1to1 FaceCenter matching join found in the distributed tree, +create a corresponding 1to1 Vertex matching join.

+

Input tree is modified inplace: Vertex GridConnectivity_t nodes +are stored under distinct containers named from the original ones, suffixed +with #Vtx. Similarly, vertex GC nodes uses the original name suffixed +with #Vtx.

+

Only unstructured-NGon based meshes are supported.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • have_isolated_faces (bool, optional) – Indicate if original joins includes +faces who does not share any edge with other external (join) faces. +If False, disable the special treatement needed by such faces (better performances, +but will fail if isolated faces were actually present). +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree_s = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+dist_tree = maia.algo.dist.convert_s_to_ngon(dist_tree_s, MPI.COMM_WORLD)
+
+maia.algo.dist.generate_jns_vertex_list(dist_tree, MPI.COMM_WORLD)
+assert len(maia.pytree.get_nodes_from_name(dist_tree, 'match*#Vtx')) == 2
+
+
+
+ +
+
+

Geometry transformations

+
+
+duplicate_from_rotation_jns_to_360(dist_tree, zone_paths, jn_paths_for_dupl, comm, conformize=False, apply_to_fields=False)
+

Reconstitute a circular mesh from an angular section of the geometry.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input distributed tree

  • +
  • zone_paths (list of str) – List of pathes (BaseName/ZoneName) of the connected zones to duplicate

  • +
  • jn_paths_for_dupl (pair of list of str) – (listA, listB) where listA (resp. list B) stores all the +pathes of the GridConnectivity nodes defining the first (resp. second) side of a periodic match.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • conformize (bool, optional) – If true, ensure that the generated interface vertices have exactly same +coordinates (see conformize_jn_pair()). Defaults to False.

  • +
  • apply_to_fields (bool, optional) – See maia.algo.transform_affine(). Defaults to False.

  • +
+
+
+
+ +
+
+merge_zones(tree, zone_paths, comm, output_path=None, subset_merge='name', concatenate_jns=True)
+

Merge the given zones into a single one.

+

Input tree is modified inplace : original zones will be removed from the tree and replaced +by the merged zone. Merged zone is added with name MergedZone under the first involved Base +except if output_path is not None : in this case, the provided path defines the base and zone name +of the merged block.

+

Subsets of the merged block can be reduced thanks to subset_merge parameter:

+
    +
  • None => no reduction occurs : all subset of all original zones remains on merged zone, with a +numbering suffix.

  • +
  • ‘name’ => Subset having the same name on the original zones (within a same label) produces +and unique subset on the output merged zone.

  • +
+

Only unstructured-NGon trees are supported, and interfaces between the zones +to merge must have a FaceCenter location.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • zone_paths (list of str) – List of path (BaseName/ZoneName) of the zones to merge

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • output_path (str, optional) – Path of the output merged block. Defaults to None.

  • +
  • subset_merge (str, optional) – Merging strategy for the subsets. Defaults to ‘name’.

  • +
  • concatenate_jns (bool, optional) – if True, reduce the multiple 1to1 matching joins related +to the merged_zone to a single one. Defaults to True.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 3
+
+maia.algo.dist.merge_zones(dist_tree, ["BaseA/blk1", "BaseB/blk2"], MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 2
+
+
+
+ +
+
+merge_zones_from_family(tree, family_name, comm, **kwargs)
+

Merge the zones belonging to the given family into a single one.

+

See merge_zones() for full documentation.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • family_name (str) – Name of the family (read from FamilyName_t node) +used to select the zones.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • kwargs – any argument of merge_zones(), excepted output_path

  • +
+
+
+
+

See also

+

Function merge_all_zones_from_families(tree, comm, **kwargs) does +this operation for all the Family_t nodes of the input tree.

+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+
+# FamilyName are not included in the mesh
+for zone in PT.get_all_Zone_t(dist_tree):
+  PT.new_child(zone, 'FamilyName', 'FamilyName_t', 'Naca0012')  
+
+maia.algo.dist.merge_zones_from_family(dist_tree, 'Naca0012', MPI.COMM_WORLD)
+
+zones = PT.get_all_Zone_t(dist_tree)
+assert len(zones) == 1 and PT.get_name(zones[0]) == 'naca0012'
+
+
+
+ +
+
+merge_connected_zones(tree, comm, **kwargs)
+

Detect all the zones connected through 1to1 matching jns and merge them.

+

See merge_zones() for full documentation.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • kwargs – any argument of merge_zones(), excepted output_path

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+
+maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 1
+
+
+
+ +
+
+conformize_jn_pair(dist_tree, jn_paths, comm)
+

Ensure that the vertices belonging to the two sides of a 1to1 GridConnectivity +have the same coordinates.

+

Matching join with Vertex or FaceCenter location are admitted. Coordinates +of vertices are made equal by computing the arithmetic mean of the two +values.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input tree

  • +
  • jn_pathes (list of str) – Pathes of the two matching GridConnectivity_t +nodes. Pathes must start from the root of the tree.

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +
+
+

Data management

+
+
+redistribute_tree(dist_tree, comm, policy='uniform')
+

Redistribute the data of the input tree according to the choosen distribution policy.

+

Supported policies are:

+
    +
  • uniform : each data array is equally reparted over all the processes

  • +
  • gather.N : all the data are moved on process N

  • +
  • gather : shortcut for gather.0

  • +
+

In both case, tree structure remains unchanged on all the processes +(the tree is still a valid distributed tree). +Input is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • policy (str) – distribution policy (see above)

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree_ini = maia.factory.generate_dist_block(21, 'Poly', MPI.COMM_WORLD)
+dist_tree_gathered = maia.algo.dist.redistribute_tree(dist_tree_ini, \
+    MPI.COMM_WORLD, policy='gather.0')
+
+
+
+ +
+
+
+

Partitioned algorithms

+

The following algorithms applies on maia partitioned trees.

+
+

Geometric calculations

+
+
+compute_cell_center(zone)
+

Compute the cell centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node. +Centers are computed using a basic average over the vertices of the cells.

+
+
Parameters
+

zone (CGNSTree) – Partitionned CGNS Zone

+
+
Returns
+

cell_center (array) – Flat (interlaced) numpy array of cell centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  cell_center = maia.algo.part.compute_cell_center(zone)
+
+
+
+ +
+
+compute_face_center(zone)
+

Compute the face centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node, and a unstructured connectivity.

+

Centers are computed using a basic average over the vertices of the faces.

+
+

Note

+

If zone is described with standard elements, centers will be computed for elements +explicitly defined in cgns tree.

+
+
+
Parameters
+

zone (CGNSTree) – Partitionned 2D or 3D U CGNS Zone

+
+
Returns
+

face_center (array) – Flat (interlaced) numpy array of face centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  face_center = maia.algo.part.compute_face_center(zone)
+
+
+
+ +
+
+compute_edge_center(zone)
+

Compute the edge centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node, and a unstructured standard elements connectivity.

+
+

Note

+

If zone is described with standard elements, centers will be computed for elements +explicitly defined in cgns tree.

+
+
+
Parameters
+

zone (CGNSTree) – Partitionned 2D or 3D U-elts CGNS Zone

+
+
Returns
+

face_center (array) – Flat (interlaced) numpy array of edge centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, "QUAD_4",  MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  edge_center = maia.algo.part.geometry.compute_edge_center(zone)
+
+
+
+ +
+
+compute_wall_distance(part_tree, comm, *, method='cloud', families=[], point_cloud='CellCenter', out_fs_name='WallDistance')
+

Compute wall distances and add it in tree.

+

For each volumic point, compute the distance to the nearest face belonging to a BC of kind wall. +Computation can be done using “cloud” or “propagation” method.

+
+

Note

+

Propagation method requires ParaDiGMa access and is only available for unstructured cell centered +NGon connectivities grids. In addition, partitions must have been created from a single initial domain +with this method.

+
+
+

Important

+

Distance are computed to the BCs belonging to one of the families specified in families list. +If list is empty, we try to auto detect wall-like families. +In both case, families are (for now) the only way to select BCs to include in wall distance computation. +BCs who directly specify their type as value are not considered.

+
+

Tree is modified inplace: computed distance are added in a FlowSolution container whose +name can be specified with out_fs_name parameter.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Input partitionned tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • method ({'cloud', 'propagation'}, optional) – Choice of method. Defaults to “cloud”.

  • +
  • point_cloud (str, optional) – Points to project on the surface. Can either be one of +“CellCenter” or “Vertex” (coordinates are retrieved from the mesh) or the name of a FlowSolution +node in which coordinates are stored. Defaults to CellCenter.

  • +
  • families (list of str) – List of families to consider as wall faces.

  • +
  • out_fs_name (str, optional) – Name of the output FlowSolution_t node storing wall distance data.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, families=["WALL"])
+assert maia.pytree.get_node_from_name(part_tree, "WallDistance") is not None
+
+
+
+ +
+
+localize_points(src_tree, tgt_tree, location, comm, **options)
+

Localize points between two partitioned trees.

+

For all the points of the target tree matching the given location, +search the cell of the source tree in which it is enclosed. +The result, i.e. the gnum & domain number of the source cell (or -1 if the point is not localized), +are stored in a DiscreteData_t container called “Localization” on the target zones.

+

Source tree must be unstructured and have a NGon connectivity.

+

Localization can be parametred thought the options kwargs:

+
    +
  • loc_tolerance (default = 1E-6) – Geometric tolerance for the method.

  • +
+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned. Only U-NGon connectivities are managed.

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned. Structured or U-NGon connectivities are managed.

  • +
  • location ({'CellCenter', 'Vertex'}) – Target points to localize

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • **options – Additional options related to location strategy

  • +
+
+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt):
+  maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3])
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+
+maia.algo.part.localize_points(part_tree_src, part_tree_tgt, 'CellCenter', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt):
+  loc_container = PT.get_child_from_name(tgt_zone, 'Localization')
+  assert PT.Subset.GridLocation(loc_container) == 'CellCenter'
+
+
+
+ +
+
+find_closest_points(src_tree, tgt_tree, location, comm)
+

Find the closest points between two partitioned trees.

+

For all points of the target tree matching the given location, +search the closest point of same location in the source tree. +The result, i.e. the gnum & domain number of the source point, are stored in a DiscreteData_t +container called “ClosestPoint” on the target zones. +The ids of source points refers to cells or vertices depending on the chosen location.

+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned

  • +
  • location ({'CellCenter', 'Vertex'}) – Entity to use to compute closest points

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt):
+  maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3])
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+
+maia.algo.part.find_closest_points(part_tree_src, part_tree_tgt, 'Vertex', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt):
+  loc_container = PT.get_child_from_name(tgt_zone, 'ClosestPoint')
+  assert PT.Subset.GridLocation(loc_container) == 'Vertex'
+
+
+
+ +
+
+

Mesh extractions

+
+
+iso_surface(part_tree, iso_field, comm, iso_val=0.0, containers_name=[], **options)
+

Create an isosurface from the provided field and value on the input partitioned tree.

+

Isosurface is returned as an independant (2d) partitioned CGNSTree.

+
+

Important

+
    +
  • Input tree must be unstructured and have a ngon connectivity.

  • +
  • Input field for isosurface computation must be located at vertices.

  • +
  • This function requires ParaDiGMa access.

  • +
+
+
+

Note

+
    +
  • Once created, additional fields can be exchanged from volumic tree to isosurface tree using +_exchange_field(part_tree, iso_part_tree, containers_name, comm).

  • +
  • If elt_type is set to ‘TRI_3’, boundaries from volumic mesh are extracted as edges on +the isosurface and FaceCenter are allowed to be exchanged.

  • +
+
+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree on which isosurf is computed. Only U-NGon +connectivities are managed.

  • +
  • iso_field (str) – Path (starting at Zone_t level) of the field to use to compute isosurface.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • iso_val (float, optional) – Value to use to compute isosurface. Defaults to 0.

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output isosurface tree.

  • +
  • **options – Options related to plane extraction.

  • +
+
+
Returns
+

isosurf_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Extraction can be controled thought the optional kwargs:

+
+
    +
  • elt_type (str) – Controls the shape of elements used to describe +the isosurface. Admissible values are TRI_3, QUAD_4, NGON_n. Defaults to TRI_3.

  • +
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, families=["WALL"], point_cloud='Vertex')
+
+part_tree_iso = maia.algo.part.iso_surface(part_tree, "WallDistance/TurbulentDistance", iso_val=0.25,\
+    containers_name=['WallDistance'], comm=MPI.COMM_WORLD)
+
+assert maia.pytree.get_node_from_name(part_tree_iso, "WallDistance") is not None
+
+
+
+ +
+
+plane_slice(part_tree, plane_eq, comm, containers_name=[], **options)
+

Create a slice from the provided plane equation \(ax + by + cz - d = 0\) +on the input partitioned tree.

+

Slice is returned as an independant (2d) partitioned CGNSTree. See iso_surface() +for use restrictions and additional advices.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree to slice. Only U-NGon connectivities are managed.

  • +
  • sphere_eq (list of float) – List of 4 floats \([a,b,c,d]\) defining the plane equation.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output slice tree.

  • +
  • **options – Options related to plane extraction (see iso_surface()).

  • +
+
+
Returns
+

slice_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD) # Isosurf requires single block mesh
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0.5], MPI.COMM_WORLD, elt_type='QUAD_4')
+
+
+
+ +
+
+spherical_slice(part_tree, sphere_eq, comm, containers_name=[], **options)
+

Create a spherical slice from the provided equation +\((x-x_0)^2 + (y-y_0)^2 + (z-z_0)^2 = R^2\) +on the input partitioned tree.

+

Slice is returned as an independant (2d) partitioned CGNSTree. See iso_surface() +for use restrictions and additional advices.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree to slice. Only U-NGon connectivities are managed.

  • +
  • plane_eq (list of float) – List of 4 floats \([x_0, y_0, z_0, R]\) defining the sphere equation.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output slice tree.

  • +
  • **options – Options related to plane extraction (see iso_surface()).

  • +
+
+
Returns
+

slice_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Example

+
from mpi4py import MPI
+import numpy
+import maia
+import maia.pytree as PT
+dist_tree = maia.factory.generate_dist_block(11, 'Poly', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD, preserve_orientation=True)
+
+# Add solution
+zone      = PT.get_node_from_label(part_tree, "Zone_t")
+vol_rank  = MPI.COMM_WORLD.Get_rank() * numpy.ones(PT.Zone.n_cell(zone))
+src_sol   = PT.new_FlowSolution('FlowSolution', loc='CellCenter', fields={'i_rank' : vol_rank}, parent=zone)
+
+slice_tree = maia.algo.part.spherical_slice(part_tree, [0.5,0.5,0.5,0.25], MPI.COMM_WORLD, \
+    ["FlowSolution"], elt_type="NGON_n")
+
+assert maia.pytree.get_node_from_name(slice_tree, "FlowSolution") is not None
+
+
+
+ +
+
+extract_part_from_zsr(part_tree, zsr_name, comm, containers_name=[], **options)
+

Extract the submesh defined by the provided ZoneSubRegion from the input volumic +partitioned tree.

+

Dimension of the output mesh is set up accordingly to the GridLocation of the ZoneSubRegion. +Submesh is returned as an independant partitioned CGNSTree and includes the relevant connectivities.

+

In addition, containers specified in containers_name list are transfered to the extracted tree. +Containers to be transfered can be either of label FlowSolution_t or ZoneSubRegion_t.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree from which extraction is computed. Only U-NGon +connectivities are managed.

  • +
  • zsr_name (str) – Name of the ZoneSubRegion_t node

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the fields containers to transfer +on the output extracted tree.

  • +
  • **options – Options related to the extraction.

  • +
+
+
Returns
+

extracted_tree (CGNSTree) – Extracted submesh (partitioned)

+
+
+

Extraction can be controled by the optional kwargs:

+
+
    +
  • graph_part_tool (str) – Partitioning tool used to balance the extracted zones. +Admissible values are hilbert, parmetis, ptscotch. Note that +vertex-located extractions require hilbert partitioning. Defaults to hilbert.

  • +
+
+
+

Important

+
    +
  • Input tree must be unstructured and have a ngon connectivity.

  • +
  • Partitions must come from a single initial domain on input tree.

  • +
+
+
+

See also

+

create_extractor_from_zsr() takes the same parameters, excepted containers_name, +and returns an Extractor object which can be used to exchange containers more than once through its +Extractor.exchange_fields(container_name) method.

+
+

Example

+
from   mpi4py import MPI
+import numpy as np
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, families=["WALL"], point_cloud='Vertex')
+
+# Create a ZoneSubRegion on procs for extracting odd cells
+for part_zone in PT.get_all_Zone_t(part_tree):
+  ncell       = PT.Zone.n_cell(part_zone)
+  start_range = PT.Element.Range(PT.Zone.NFaceNode(part_zone))[0]
+  point_list  = np.arange(start_range, start_range+ncell, 2, dtype=np.int32).reshape((1,-1), order='F')
+  PT.new_ZoneSubRegion(name='ZoneSubRegion', point_list=point_list, loc='CellCenter', parent=part_zone)
+
+extracted_tree = maia.algo.part.extract_part_from_zsr(part_tree, 'ZoneSubRegion', MPI.COMM_WORLD,
+                                                      containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_tree, "WallDistance") is not None
+
+
+
+ +
+
+extract_part_from_bc_name(part_tree, bc_name, comm, transfer_dataset=True, containers_name=[], **options)
+

Extract the submesh defined by the provided BC name from the input volumic +partitioned tree.

+

Behaviour and arguments of this function are similar to those of extract_part_from_zsr() +(zsr_name becomes bc_name). Optional transfer_dataset argument allows to +transfer BCDataSet from BC to the extracted mesh (default to True).

+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, families=["WALL"], point_cloud='Vertex')
+
+extracted_bc = maia.algo.part.extract_part_from_bc_name(part_tree, \
+               'wall', MPI.COMM_WORLD, containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_bc, "WallDistance") is not None
+
+
+
+ +
+
+

Interpolations

+
+
+interpolate_from_part_trees(src_tree, tgt_tree, comm, containers_name, location, **options)
+

Interpolate fields between two partitionned trees.

+

For now, interpolation is limited to lowest order: target points take the value of the +closest point (or their englobing cell, depending of choosed options) in the source mesh. +Interpolation strategy can be controled thought the options kwargs:

+
    +
  • strategy (default = ‘Closest’) – control interpolation method

    +
      +
    • ‘Closest’ : Target points take the value of the closest source cell center.

    • +
    • ‘Location’ : Target points take the value of the cell in which they are located. +Unlocated points have take a NaN value.

    • +
    • ‘LocationAndClosest’ : Use ‘Location’ method and then ‘ClosestPoint’ method +for the unlocated points.

    • +
    +
  • +
  • loc_tolerance (default = 1E-6) – Geometric tolerance for Location method.

  • +
+
+

Important

+
    +
  • Source fields must be located at CellCenter.

  • +
  • Source tree must be unstructured and have a ngon connectivity.

  • +
+
+
+

See also

+

create_interpolator_from_part_trees() takes the same parameters, excepted containers_name, +and returns an Interpolator object which can be used to exchange containers more than once through its +Interpolator.exchange_fields(container_name) method.

+
+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned. Only U-NGon connectivities are managed.

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned. Structured or U-NGon connectivities are managed.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the source FlowSolution_t nodes to transfer.

  • +
  • location ({'CellCenter', 'Vertex'}) – Expected target location of the fields.

  • +
  • **options – Options related to interpolation strategy

  • +
+
+
+

Example

+
import mpi4py
+import numpy
+import maia
+import maia.pytree as PT
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.factory.generate_dist_block(11, 'Poly', comm)
+dist_tree_tgt = maia.factory.generate_dist_block(20, 'Poly', comm)
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+# Create fake solution
+zone = maia.pytree.get_node_from_label(part_tree_src, "Zone_t")
+src_sol = maia.pytree.new_FlowSolution('FlowSolution', loc='CellCenter', parent=zone)
+PT.new_DataArray("Field", numpy.random.rand(PT.Zone.n_cell(zone)), parent=src_sol)
+
+maia.algo.part.interpolate_from_part_trees(part_tree_src, part_tree_tgt, comm,\
+    ['FlowSolution'], 'Vertex')
+tgt_sol = PT.get_node_from_name(part_tree_tgt, 'FlowSolution')
+assert tgt_sol is not None and PT.Subset.GridLocation(tgt_sol) == 'Vertex'
+
+
+
+ +
+
+centers_to_nodes(tree, comm, containers_name=[], **options)
+

Create Vertex located FlowSolution_t from CellCenter located FlowSolution_t.

+

Interpolation is based on Inverse Distance Weighting +(IDW) method: +each cell contributes to each of its vertices with a weight computed from the distance +between the cell isobarycenter and the vertice. The method can be tuned with +the following kwargs:

+
    +
  • idw_power (float, default = 1) – Power to which the cell-vertex distance is elevated.

  • +
  • cross_domain (bool, default = True) – If True, vertices located at domain +interfaces also receive data from the opposite domain cells. This parameter does not +apply to internal partitioning interfaces, which are always crossed.

  • +
+
+
Parameters
+
    +
  • tree (CGNSTree) – Partionned tree. Only unstructured connectivities are managed.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer.

  • +
  • **options – Options related to interpolation, see above.

  • +
+
+
+
+

See also

+

A CenterToNode object can be instanciated with the same parameters, excluding containers_name, +and then be used to move containers more than once with its +move_fields(container_name) method.

+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Init a FlowSolution located at Cells
+for part in PT.get_all_Zone_t(part_tree):
+  cell_center = maia.algo.part.compute_cell_center(part)
+  fields = {'ccX': cell_center[0::3], 'ccY': cell_center[1::3], 'ccZ': cell_center[2::3]}
+  PT.new_FlowSolution('FSol', loc='CellCenter', fields=fields, parent=part)
+
+maia.algo.part.centers_to_nodes(part_tree, comm, ['FSol'])
+
+for part in PT.get_all_Zone_t(part_tree):
+  vtx_sol = PT.get_node_from_name(part, 'FSol#Vtx')
+  assert PT.Subset.GridLocation(vtx_sol) == 'Vertex'
+
+
+
+ +
+
+
+

Generic algorithms

+

The following algorithms applies on maia distributed or partitioned trees

+
+
+transform_affine(t, rotation_center=array([0., 0., 0.]), rotation_angle=array([0., 0., 0.]), translation=array([0., 0., 0.]), apply_to_fields=False)
+

Apply the affine transformation to the coordinates of the given zone.

+

Input zone(s) can be either structured or unstructured, but must have cartesian coordinates. +Transformation is defined by

+
+\[\tilde v = R \cdot (v - c) + c + t\]
+

where c, t are the rotation center and translation vector and R is the rotation matrix.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher.

  • +
  • rotation_center (array) – center coordinates of the rotation

  • +
  • rotation_angler (array) – angles of the rotation

  • +
  • translation (array) – translation vector components

  • +
  • apply_to_fields (bool, optional) – if True, apply the rotation vector to the vectorial fields found under +following nodes : FlowSolution_t, DiscreteData_t, ZoneSubRegion_t, BCDataset_t. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+zone = maia.pytree.get_all_Zone_t(dist_tree)[0]
+
+maia.algo.transform_affine(zone, translation=[3,0,0])
+
+
+
+ +
+
+pe_to_nface(t, comm=None, removePE=False)
+

Create a NFace node from a NGon node with ParentElements.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Distributed or Partitioned tree (or sequences of) +starting at Zone_t level or higher.

  • +
  • comm (MPIComm) – MPI communicator, mandatory only for distributed zones

  • +
  • remove_PE (bool, optional) – If True, remove the ParentElements node. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+tree = maia.factory.generate_dist_block(6, 'Poly', MPI.COMM_WORLD)
+
+for zone in maia.pytree.get_all_Zone_t(tree):
+  maia.algo.pe_to_nface(zone, MPI.COMM_WORLD)
+  assert maia.pytree.get_child_from_name(zone, 'NFaceElements') is not None
+
+
+
+ +
+
+nface_to_pe(t, comm=None, removeNFace=False)
+

Create a ParentElements node in the NGon node from a NFace node.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Distributed or Partitioned tree (or sequences of) +starting at Zone_t level or higher.

  • +
  • comm (MPIComm) – MPI communicator, mandatory only for distributed zones

  • +
  • removeNFace (bool, optional) – If True, remove the NFace node. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+tree = maia.factory.generate_dist_block(6, 'NFace_n', MPI.COMM_WORLD)
+
+maia.algo.nface_to_pe(tree, MPI.COMM_WORLD)
+assert maia.pytree.get_node_from_name(tree, 'ParentElements') is not None
+
+
+
+ +
+
+

Sequential algorithms

+

The following algorithms applies on regular pytrees.

+
+
+poly_new_to_old(tree, full_onera_compatibility=True)
+

Transform a tree with polyhedral unstructured connectivity with new CGNS 4.x conventions to old CGNS 3.x conventions.

+

The tree is modified in place.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree described with new CGNS convention.

  • +
  • full_onera_compatibility (bool) – if True, shift NFace and ParentElements ids to begin at 1, irrespective of the NGon and NFace ElementRanges, and make the NFace connectivity unsigned

  • +
+
+
+
+ +
+
+poly_old_to_new(tree)
+

Transform a tree with polyhedral unstructured connectivity with old CGNS 3.x conventions to new CGNS 4.x conventions.

+

The tree is modified in place.

+

This function accepts trees with old ONERA conventions where NFace and ParentElements ids begin at 1, irrespective of the NGon and NFace ElementRanges, and where the NFace connectivity is unsigned. The resulting tree has the correct CGNS/SIDS conventions.

+
+
Parameters
+

tree (CGNSTree) – Tree described with old CGNS convention.

+
+
+
+ +
+
+enforce_ngon_pe_local(t)
+

Shift the ParentElements values in order to make it start at 1, as requested by legacy tools.

+

The tree is modified in place.

+
+
Parameters
+

t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher.

+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/user_manual/config.html b/docs/1.0/user_manual/config.html new file mode 100644 index 00000000..40b371b6 --- /dev/null +++ b/docs/1.0/user_manual/config.html @@ -0,0 +1,334 @@ + + + + + + + + + + Configuration — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Configuration

+
+

Logging

+

Maia provides informations to the user through the loggers summarized +in the following table:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +

Logger

Purpose

Default printer

maia

Light general info

mpi_rank_0_stdout_printer

maia-warnings

Warnings

mpi_rank_0_stdout_printer

maia-errors

Errors

mpi_rank_0_stdout_printer

maia-stats

More detailed timings +and memory usage

No output

+

The easiest way to change this default configuration is to +set the environment variable LOGGING_CONF_FILE to provide a file +(e.g. logging.conf) looking like this:

+
maia          : mpi_stdout_printer        # All ranks output to sdtout
+maia-warnings :                           # No output
+maia-errors   : mpi_rank_0_stderr_printer # Rank 0 output to stderr
+maia-stats    : file_printer("stats.log") # All ranks output in the file
+
+
+

See Log management for a full description of available +printers or for instructions on how to create your own printers. +This page also explain how to dynamically change printers +directly in your Python application.

+
+
+

Exception handling

+

Maia automatically override the sys.excepthook +function to call MPI_Abort when an uncatched exception occurs. +This allow us to terminate the MPI execution and to avoid some deadlocks +if an exception is raised by a single process. +Note that this call force the global COMM_WORLD communicator to abort which +can have undesired effects if sub communicators are used.

+

This behaviour can be disabled with a call to +maia.excepthook.disable_mpi_excepthook().

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/user_manual/factory.html b/docs/1.0/user_manual/factory.html new file mode 100644 index 00000000..bec291f3 --- /dev/null +++ b/docs/1.0/user_manual/factory.html @@ -0,0 +1,720 @@ + + + + + + + + + + Factory module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Factory module

+

The maia.factory module can be used to generate parallel CGNS trees. Those can +be obtained from an other kind of tree, or can be generated from user parameters.

+
+

Generation

+
+
+generate_dist_block(n_vtx, cgns_elmt_name, comm, origin=array([0., 0., 0.]), edge_length=1.0)
+

Generate a distributed mesh with a cartesian topology.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +and cell dimension of the zone is controled by the cgns_elmt_name parameter:

+
    +
  • "Structured" (or "S") produces a 3d structured zone (not yet implemented),

  • +
  • "Poly" produces an unstructured 3d zone with a NGon+PE connectivity,

  • +
  • "NFACE_n" produces an unstructured 3d zone with a NFace+NGon connectivity,

  • +
  • "NGON_n" produces an unstructured 2d zone with faces described by a NGon +node (not yet implemented),

  • +
  • Other names must be in ["TRI_3", "QUAD_4", "TETRA_4", "PYRA_5", "PENTA_6", "HEXA_8"] +and produces an unstructured 2d or 3d zone with corresponding standard elements.

  • +
+

In all cases, the created zone contains the cartesian grid coordinates and the relevant number +of boundary conditions.

+

When creating 2 dimensional zones, the +physical dimension +is set equal to the length of the origin parameter.

+
+
Parameters
+
    +
  • n_vtx (int or array of int) – Number of vertices in each direction. Scalars +automatically extend to uniform array.

  • +
  • cgns_elmt_name (str) – requested kind of elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • edge_length (float, optional) – Edge size of the generated mesh. Defaults to 1.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+zone = PT.getNodeFromType(dist_tree, 'Zone_t')
+assert PT.Element.CGNSName(PT.getNodeFromType(zone, 'Elements_t')) == 'NGON_n'
+
+dist_tree = maia.factory.generate_dist_block(10, 'TETRA_4', MPI.COMM_WORLD)
+zone = PT.getNodeFromType(dist_tree, 'Zone_t')
+assert PT.Element.CGNSName(PT.getNodeFromType(zone, 'Elements_t')) == 'TETRA_4'
+
+
+
+ +
+
+distribute_tree(tree, comm, owner=None)
+

Generate a distributed tree from a standard (full) CGNS Tree.

+

Input tree can be defined on a single process (using owner = rank_id), +or a copy can be known by all the processes (using owner=None).

+

In both cases, output distributed tree will be equilibrated over all the processes.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Full (not distributed) tree.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • owner (int, optional) – MPI rank holding the input tree. Defaults to None.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+comm = MPI.COMM_WORLD
+
+if comm.Get_rank() == 0:
+  tree = maia.io.read_tree(mesh_dir/'S_twoblocks.yaml', comm)
+else:
+  tree = None
+dist_tree = maia.factory.distribute_tree(tree, comm, owner=0)
+
+
+
+ +
+
+

Partitioning

+
+
+partition_dist_tree(dist_tree, comm, **kwargs)
+

Perform the partitioning operation: create a partitioned tree from the input distributed tree.

+

The input tree can be structured or unstuctured, but hybrid meshes are not yet supported.

+
+

Important

+

Geometric information (such as boundary conditions, zone subregion, etc.) are reported +on the partitioned tree; however, data fields (BCDataSet, FlowSolution, etc.) are not +transfered automatically. See maia.transfer module.

+
+

See reference documentation for the description of the keyword arguments.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • **kwargs – Partitioning options

  • +
+
+
Returns
+

CGNSTree – partitioned cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+comm = MPI.COMM_WORLD
+i_rank, n_rank = comm.Get_rank(), comm.Get_size()
+dist_tree  = maia.factory.generate_dist_block(10, 'Poly', comm)
+
+#Basic use
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+#Crazy partitioning where each proc get as many partitions as its rank
+n_part_tot = n_rank * (n_rank + 1) // 2
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm, \
+    zone_to_parts={'Base/zone' : [1./n_part_tot for i in range(i_rank+1)]})
+assert len(maia.pytree.get_all_Zone_t(part_tree)) == i_rank+1
+
+
+
+ +
+

Partitioning options

+

Partitioning can be customized with the following keywords arguments:

+
+
+graph_part_tool
+

Graph partitioning library to use to split unstructured blocks. Irrelevent for structured blocks.

+
+
Admissible values
+

parmetis, ptscotch, hilbert. Blocks defined by nodal elements does +not support hilbert method.

+
+
Default value
+

parmetis, if installed; else ptscotch, if installed; hilbert otherwise.

+
+
+
+ +
+
+zone_to_parts
+

Control the number, size and repartition of partitions. See Repartition.

+
+
Default value
+

Computed such that partitioning is balanced using +maia.factory.partitioning.compute_balanced_weights().

+
+
+
+ +
+
+part_interface_loc
+

GridLocation for the created partitions interfaces. Pre-existing interface keep their original location.

+
+
Admissible values
+

FaceCenter, Vertex

+
+
Default value
+

FaceCenter for unstructured zones with NGon connectivities; Vertex otherwise.

+
+
+
+ +
+
+reordering
+

Dictionnary of reordering options, which are used to renumber the entities in each partitioned zone. See +corresponding documentation.

+
+ +
+
+preserve_orientation
+

If True, the created interface faces are not reversed and keep their original orientation. Consequently, +NGonElements can have a zero left parent and a non zero right parent. +Only relevant for U/NGon partitions.

+
+
Default value
+

False

+
+
+
+ +
+
+dump_pdm_output
+

If True, dump the raw arrays created by paradigm in a CGNSNode at (partitioned) zone level. For debug only.

+
+
Default value
+

False

+
+
+
+ +
+
+

Repartition

+

The number, size, and repartition (over the processes) of the created partitions is +controlled through the zone_to_parts keyword argument: each process must provide a +dictionary associating the path of every distributed zone to a list of floats. +For a given distributed zone, current process will receive as many partitions +as the length of the list (missing path or empty list means no partitions); the size of these partitions +corresponds to the values in the list (expressed in fraction of the input zone). +For a given distributed zone, the sum of all the fractions across all the processes must +be 1.

+

This dictionary can be created by hand; for convenience, Maia provides three functions in the +maia.factory.partitioning module to create this dictionary.

+
+
+compute_regular_weights(tree, comm, n_part=1)
+

Compute a basic zone_to_parts repartition.

+

Each process request n_part partitions on each original zone (n_part can differ +for each proc). +The weights of all the parts produced from a given zone are homogeneous +and equal to the number of cells in the zone divided by the total +number of partitions requested for this zone.

+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
  • n_part (int,optional) – Number of partitions to produce on each zone by the proc. +Defaults to 1.

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+zone_to_parts = mpart.compute_regular_weights(dist_tree, MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_size() == 2:
+  assert zone_to_parts == {'Base/Large': [0.5], 'Base/Small': [0.5]}
+
+
+
+ +
+
+compute_balanced_weights(tree, comm, only_uniform=False)
+

Compute a well balanced zone_to_parts repartition.

+

Each process request or not partitions with heterogeneous weight on each +original zone such that:

+
    +
  • the computational load is well balanced, ie the total number of +cells per process is nearly equal,

  • +
  • the number of splits within a given zone is minimized,

  • +
  • produced partitions are not too small.

  • +
+
+

Note

+

Heterogeneous weights are not managed by ptscotch. Use parmetis as graph_part_tool +for partitioning if repartition was computed with this function, or set optional +argument only_uniform to True.

+
+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
  • only_uniform (bool, optional) – If true, an alternative balancing method is used +in order to request homogeneous weights, but load balance is less equilibrated. +Default to False.

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+comm = MPI.COMM_WORLD
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', comm)
+
+zone_to_parts = mpart.compute_balanced_weights(dist_tree, comm)
+if comm.Get_size() == 2 and comm.Get_rank() == 0:
+  assert zone_to_parts == {'Base/Large': [0.375], 'Base/Small': [1.0]}
+if comm.Get_size() == 2 and comm.Get_rank() == 1:
+  assert zone_to_parts == {'Base/Large': [0.625]}
+
+
+
+ +
+
+compute_nosplit_weights(tree, comm)
+

Compute a zone_to_parts repartition without splitting the blocks.

+

The initial blocks will be simply distributed over the available processes, +minimizing the total number of cells affected to a proc. This leads to a poor load +balancing and possibly to procs having no partitions at all.

+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+zone_to_parts = mpart.compute_nosplit_weights(dist_tree, MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_size() == 2:
+  assert len(zone_to_parts) == 1
+
+
+
+ +
+
+

Reordering options

+

For unstructured zones, the reordering options are transmitted to ParaDiGM in order to +control the renumbering of mesh entities in the partitions.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Kwarg

Admissible values

Effect

Default

cell_renum_method

“NONE”, “RANDOM”, “HILBERT”, +“CUTHILL”, “CACHEBLOCKING”, +“CACHEBLOCKING2”, “HPC”

Renumbering method for the cells

NONE

face_renum_method

“NONE”, “RANDOM”, “LEXICOGRAPHIC”

Renumbering method for the faces

NONE

vtx_renum_method

“NONE”, “SORT_INT_EXT”

Renumbering method for the vertices

NONE

n_cell_per_cache

Integer >= 0

Specific to cacheblocking

0

n_face_per_pack

Integer >= 0

Specific to cacheblocking

0

graph_part_tool

“parmetis”, “ptscotch”, +“hyperplane”

Graph partitioning library to +use for renumbering

Same as partitioning tool

+
+
+
+

Recovering from partitions

+
+
+recover_dist_tree(part_tree, comm)
+

Regenerate a distributed tree from a partitioned tree.

+

The partitioned tree should have been created using Maia, or +must at least contains GlobalNumbering nodes as defined by Maia +(see Partitioned trees).

+

The following nodes are managed : GridCoordinates, Elements, ZoneBC, ZoneGridConnectivity +FlowSolution, DiscreteData and ZoneSubRegion.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned CGNS Tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+comm = MPI.COMM_WORLD
+
+dist_tree_bck  = maia.factory.generate_dist_block(5, 'TETRA_4', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree_bck, comm)
+
+dist_tree = maia.factory.recover_dist_tree(part_tree, comm)
+assert maia.pytree.is_same_tree(dist_tree, dist_tree_bck)
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/user_manual/io.html b/docs/1.0/user_manual/io.html new file mode 100644 index 00000000..b1001cd0 --- /dev/null +++ b/docs/1.0/user_manual/io.html @@ -0,0 +1,477 @@ + + + + + + + + + + File management — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

File management

+

Maia supports HDF5/CGNS file reading and writing, +see related documention.

+

The IO functions are provided by the maia.io module. All the high level functions +accepts a legacy parameter used to control the low level CGNS-to-hdf driver:

+
    +
  • if legacy==False (default), hdf calls are performed by the python module +h5py.

  • +
  • if legacy==True, hdf calls are performed by +Cassiopee.Converter module.

  • +
+

The requested driver should be installed on your computer as well as the +hdf5 library compiled with parallel support.

+
+

Distributed IO

+

Distributed IO is the privileged way to deal with CGNS files within your maia workflows. +Files are loaded as distributed trees, and, inversely, distributed trees can be written +into a single CGNS file.

+

High level IO operations can be performed with the two following functions, which read +or write all data they found :

+
+
+file_to_dist_tree(filename, comm, legacy=False)
+

Distributed load of a CGNS file.

+
+
Parameters
+
    +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
Returns
+

CGNSTree – Distributed CGNS tree

+
+
+
+ +
+
+dist_tree_to_file(dist_tree, filename, comm, legacy=False)
+

Distributed write to a CGNS file.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree to write

  • +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +

The example below shows how to uses these high level functions:

+
from mpi4py import MPI
+import maia
+
+# Generate a sample tree
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+# Write
+maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+# Read
+tree = maia.io.file_to_dist_tree("tree.cgns", MPI.COMM_WORLD)
+
+
+

Finer control of what is written or loaded can be achieved with the following steps:

+
    +
  • For a write operation, the easiest way to write only some nodes in +the file is to remove the unwanted nodes from the distributed tree.

  • +
  • For a read operation, the load has to be divided into the following steps:

    +
      +
    • Loading a size_tree: this tree has only the shape of the distributed data and +not the data itself.

    • +
    • Removing unwanted nodes in the size tree;

    • +
    • Fill the filtered tree from the file.

    • +
    +
  • +
+

The example below illustrate how to filter the written or loaded nodes:

+
from mpi4py import MPI
+import maia
+
+# Generate a sample tree
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+
+# Remove the nodes we do not want to write
+maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateZ') #This is a DataArray
+maia.pytree.rm_nodes_from_name(dist_tree, 'Zm*') #This is some BC nodes
+# Write
+maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+# Read
+from maia.io.cgns_io_tree import load_collective_size_tree, fill_size_tree
+dist_tree = load_collective_size_tree("tree.cgns", MPI.COMM_WORLD)
+#For now dist_tree only contains sizes : let's filter it
+maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateY') #This is a DataArray
+maia.pytree.rm_nodes_from_name(dist_tree, 'Ym*') #This is some BC nodes
+fill_size_tree(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+
+
+
+

Writing partitioned trees

+

In some cases, it may be useful to write a partitioned tree (keeping the +partitioned zones separated). This can be achieved using the following function:

+
+
+part_tree_to_file(part_tree, filename, comm, single_file=False, legacy=False)
+

Gather the partitioned zones managed by all the processes and write it in a unique +hdf container.

+

If single_file is True, one file named filename storing all the partitioned +zones is written. Otherwise, hdf links are used to produce a main file filename +linking to additional subfiles.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree

  • +
  • filename (str) – Path of the output file

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • single_file (bool) – Produce a unique file if True; use CGNS links otherwise.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.io.part_tree_to_file(part_tree, 'part_tree.cgns', MPI.COMM_WORLD)
+
+
+
+ +
+
+

Raw IO

+

For debug purpose, trees can also be read or written independently in a +sequential manner. Be aware that information added by maia such as Distribution +or GlobalNumbering nodes will not be removed.

+
+
+read_tree(filename, legacy=False)
+

Sequential load of a CGNS file.

+
+
Parameters
+

filename (str) – Path of the file

+
+
Returns
+

CGNSTree – Full (not distributed) CGNS tree

+
+
+
+ +
+
+write_tree(tree, filename, links=[], legacy=False)
+

Sequential write to a CGNS file.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree to write

  • +
  • filename (str) – Path of the file

  • +
  • links (list) – List of links to create (see SIDS-to-Python guide)

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_rank() == 0:
+  maia.io.write_tree(dist_tree, "tree.cgns")
+
+
+
+ +
+
+write_trees(tree, filename, comm, legacy=False)
+

Sequential write to CGNS files.

+

Write separate trees for each process. Rank id will be automatically +inserted in the filename.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree to write

  • +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+maia.io.write_trees(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/user_manual/transfer.html b/docs/1.0/user_manual/transfer.html new file mode 100644 index 00000000..a79cf10c --- /dev/null +++ b/docs/1.0/user_manual/transfer.html @@ -0,0 +1,497 @@ + + + + + + + + + + Transfer module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Transfer module

+

The maia.transfer contains functions that exchange data between the +partitioned and distributed meshes.

+
+

Fields transfer

+

High level APIs allow to exchange data at CGNS Tree or Zone level. +All the provided functions take similar parameters: a distributed tree (resp. zone), +the corresponding partitioned tree (resp. list of zones), the MPI communicator and +optionally a filtering parameter.

+

The following kind of data are supported: +FlowSolution_t, DiscreteData_t, ZoneSubRegion_t and BCDataSet_t.

+

When transferring from distributed meshes to partitioned meshes, fields are supposed +to be known on the source mesh across all the ranks (according to disttree definition). +Geometric patches (such as ZoneSubRegion or BCDataSet) must exists on the relevant partitions, meaning +that only fields are transfered.

+

When transferring from partitioned meshes to distributed meshes, geometric patches may or may not exist on the +distributed zone: they will be created if needed. This allows to transfer fields that have been created +on the partitioned meshes. +It is however assumed that global data (e.g. FlowSolution) are defined on every partition.

+
+

Tree level

+

All the functions of this section operate inplace and require the following parameters:

+
    +
  • dist_tree (CGNSTree) – Distributed CGNS Tree

  • +
  • part_tree (CGNSTree) – Corresponding partitioned CGNS Tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+dist_tree_to_part_tree_all(dist_tree, part_tree, comm)
+

Transfer all the data fields from a distributed tree +to the corresponding partitioned tree.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+dist_tree = maia.io.file_to_dist_tree(filename, MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+maia.transfer.dist_tree_to_part_tree_all(dist_tree, part_tree, MPI.COMM_WORLD)
+
+zone = PT.get_all_Zone_t(part_tree)[0]
+assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_tree_to_dist_tree_all(dist_tree, part_tree, comm)
+

Transfer all the data fields from a partitioned tree +to the corresponding distributed tree.

+
+ +

In addition, the next two methods expect the parameter labels (list of str) +which allow to pick one or more kind of data to transfer from the supported labels.

+
+
+dist_tree_to_part_tree_only_labels(dist_tree, part_tree, labels, comm)
+

Transfer all the data fields of the specified labels from a distributed tree +to the corresponding partitioned tree.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['FlowSolution_t', 'ZoneSubRegion_t'], comm)
+
+zone = PT.get_all_Zone_t(part_tree)[0]
+assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None
+
+maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['BCDataSet_t'], comm)
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_tree_to_dist_tree_only_labels(dist_tree, part_tree, labels, comm)
+

Transfer all the data fields of the specified labels from a partitioned tree +to the corresponding distributed tree.

+
+ +
+
+

Zone level

+

All the functions of this section operate inplace and require the following parameters:

+
    +
  • dist_zone (CGNSTree) – Distributed CGNS Zone

  • +
  • part_zones (list of CGNSTree) – Corresponding partitioned CGNS Zones

  • +
  • comm (MPIComm) – MPI communicator

  • +
+

In addition, filtering is possible with the use of the +include_dict or exclude_dict dictionaries. These dictionaries map +each supported label to a list of cgns paths to include (or exclude). Paths +starts from the Zone_t node and ends at the targeted DataArray_t node. +Wildcard * are allowed in paths : for example, considering the following tree +structure,

+
Zone (Zone_t)
+├── FirstSolution (FlowSolution_t)
+│   ├── Pressure (DataArray_t)
+│   ├── MomentumX (DataArray_t)
+│   └── MomentumY (DataArray_t)
+├── SecondSolution (FlowSolution_t)
+│   ├── Pressure (DataArray_t)
+│   ├── MomentumX (DataArray_t)
+│   └── MomentumY (DataArray_t)
+└── SpecialSolution (FlowSolution_t)
+    ├── Density (DataArray_t)
+    └── MomentumZ (DataArray_t)
+
+
+
+
"FirstSolution/Momentum*" maps to ["FirstSolution/MomentumX", "FirstSolution/MomentumY"],
+
"*/Pressure maps to ["FirstSolution/Pressure", "SecondSolution/Pressure"], and
+
"S*/M*" maps to ["SecondSolution/MomentumX", "SecondSolution/MomentumY", "SpecialSolution/MomentumZ"].
+
+

For convenience, we also provide the magic path ['*'] meaning “everything related to this +label”.

+

Lastly, we use the following rules to manage missing label keys in dictionaries:

+
+
    +
  • For _only functions, we do not transfer any field related to the missing labels;

  • +
  • For _all functions, we do transfer all the fields related to the missing labels.

  • +
+
+
+
+dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict)
+

Transfer the data fields specified in include_dict from a distributed zone +to the corresponding partitioned zones.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+dist_zone = PT.get_all_Zone_t(dist_tree)[0]
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+part_zones = PT.get_all_Zone_t(part_tree)
+
+include_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'],
+                'BCDataSet_t'    : ['*']}
+maia.transfer.dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict)
+
+for zone in part_zones:
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+  assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is     None
+  assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_zones_to_dist_zone_only(dist_zone, part_zones, comm, include_dict)
+

Transfer the data fields specified in include_dict from the partitioned zones +to the corresponding distributed zone.

+
+ +
+
+dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict={})
+

Transfer all the data fields, excepted those specified in exclude_dict, +from a distributed zone to the corresponding partitioned zones.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+dist_zone = PT.get_all_Zone_t(dist_tree)[0]
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+part_zones = PT.get_all_Zone_t(part_tree)
+
+exclude_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'],
+                'BCDataSet_t'    : ['*']}
+maia.transfer.dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict)
+
+for zone in part_zones:
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is     None
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataY') is not None
+  assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+  assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None
+
+
+
+ +
+
+part_zones_to_dist_zone_all(dist_zone, part_zones, comm, exclude_dict={})
+

Transfer all the data fields, excepted those specified in exclude_dict, +from the partitioned zone to the corresponding distributed zone.

+
+ +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.0/user_manual/user_manual.html b/docs/1.0/user_manual/user_manual.html new file mode 100644 index 00000000..1fd349d8 --- /dev/null +++ b/docs/1.0/user_manual/user_manual.html @@ -0,0 +1,297 @@ + + + + + + + + + + User Manual — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

User Manual

+

Maia methods are accessible through three main modules :

+
    +
  • Factory allows to generate Maia trees, generally from another kind of tree +(e.g. the partitioning operation). Factory functions return a new tree +whose nature generally differs from the input tree.

  • +
  • Algo is the main Maia module and provides parallel algorithms to be applied on Maia trees. +Some algorithms are only available for distributed trees +and some are only available for partitioned trees. +A few algorithms are implemented for both kind +of trees and are thus directly accessible through the algo module.

    +

    Algo functions either modify their input tree inplace, or return some data, but they do not change the nature +of the tree.

    +
  • +
  • Transfer is a small module allowing to transfer data between Maia trees. A transfer function operates +on two existing trees and enriches the destination tree with data fields of the source tree.

  • +
+

Using Maia trees in your application often consists in chaining functions from these different modules.

+../_images/workflow.svg

A typical workflow could be:

+
    +
  1. Load a structured tree from a file, which produces a dist tree.

  2. +
  3. Apply some distributed algorithms to this tree: for example a structured to unstructured conversion (algo.dist module).

  4. +
  5. Generate a corresponding partitionned tree (factory module).

  6. +
  7. Apply some partitioned algorithms to the part tree, such as wall distance computation (algo.part module), +and even call you own tools (e.g. a CFD solver)

  8. +
  9. Transfer the resulting fields to the dist tree (transfer module).

  10. +
  11. Save the updated dist tree to disk.

  12. +
+

This user manuel describes the main functions available in each module.

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.0 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/.buildinfo b/docs/1.1/.buildinfo new file mode 100644 index 00000000..706a4062 --- /dev/null +++ b/docs/1.1/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 1cc18b109830259129422ccaa3e4bf34 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/1.1/.doctrees/developer_manual/algo_description.doctree b/docs/1.1/.doctrees/developer_manual/algo_description.doctree new file mode 100644 index 00000000..ae355173 Binary files /dev/null and b/docs/1.1/.doctrees/developer_manual/algo_description.doctree differ diff --git a/docs/1.1/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree b/docs/1.1/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree new file mode 100644 index 00000000..b4a2ba55 Binary files /dev/null and b/docs/1.1/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree differ diff --git a/docs/1.1/.doctrees/developer_manual/developer_manual.doctree b/docs/1.1/.doctrees/developer_manual/developer_manual.doctree new file mode 100644 index 00000000..9727f928 Binary files /dev/null and b/docs/1.1/.doctrees/developer_manual/developer_manual.doctree differ diff --git a/docs/1.1/.doctrees/developer_manual/logging.doctree b/docs/1.1/.doctrees/developer_manual/logging.doctree new file mode 100644 index 00000000..be96deb2 Binary files /dev/null and b/docs/1.1/.doctrees/developer_manual/logging.doctree differ diff --git a/docs/1.1/.doctrees/developer_manual/maia_dev/conventions.doctree b/docs/1.1/.doctrees/developer_manual/maia_dev/conventions.doctree new file mode 100644 index 00000000..ce9c34f6 Binary files /dev/null and b/docs/1.1/.doctrees/developer_manual/maia_dev/conventions.doctree differ diff --git a/docs/1.1/.doctrees/developer_manual/maia_dev/development_workflow.doctree b/docs/1.1/.doctrees/developer_manual/maia_dev/development_workflow.doctree new file mode 100644 index 00000000..731aaaf2 Binary files /dev/null and b/docs/1.1/.doctrees/developer_manual/maia_dev/development_workflow.doctree differ diff --git a/docs/1.1/.doctrees/environment.pickle b/docs/1.1/.doctrees/environment.pickle new file mode 100644 index 00000000..19d5db3a Binary files /dev/null and b/docs/1.1/.doctrees/environment.pickle differ diff --git a/docs/1.1/.doctrees/index.doctree b/docs/1.1/.doctrees/index.doctree new file mode 100644 index 00000000..70dbe376 Binary files /dev/null and b/docs/1.1/.doctrees/index.doctree differ diff --git a/docs/1.1/.doctrees/installation.doctree b/docs/1.1/.doctrees/installation.doctree new file mode 100644 index 00000000..6f416c11 Binary files /dev/null and b/docs/1.1/.doctrees/installation.doctree differ diff --git a/docs/1.1/.doctrees/introduction/introduction.doctree b/docs/1.1/.doctrees/introduction/introduction.doctree new file mode 100644 index 00000000..0f2337d1 Binary files /dev/null and b/docs/1.1/.doctrees/introduction/introduction.doctree differ diff --git a/docs/1.1/.doctrees/license.doctree b/docs/1.1/.doctrees/license.doctree new file mode 100644 index 00000000..09d331ba Binary files /dev/null and b/docs/1.1/.doctrees/license.doctree differ diff --git a/docs/1.1/.doctrees/quick_start.doctree b/docs/1.1/.doctrees/quick_start.doctree new file mode 100644 index 00000000..b50f9232 Binary files /dev/null and b/docs/1.1/.doctrees/quick_start.doctree differ diff --git a/docs/1.1/.doctrees/related_projects.doctree b/docs/1.1/.doctrees/related_projects.doctree new file mode 100644 index 00000000..daf69886 Binary files /dev/null and b/docs/1.1/.doctrees/related_projects.doctree differ diff --git a/docs/1.1/.doctrees/releases/release_notes.doctree b/docs/1.1/.doctrees/releases/release_notes.doctree new file mode 100644 index 00000000..69a64441 Binary files /dev/null and b/docs/1.1/.doctrees/releases/release_notes.doctree differ diff --git a/docs/1.1/.doctrees/user_manual/algo.doctree b/docs/1.1/.doctrees/user_manual/algo.doctree new file mode 100644 index 00000000..6e1ad243 Binary files /dev/null and b/docs/1.1/.doctrees/user_manual/algo.doctree differ diff --git a/docs/1.1/.doctrees/user_manual/config.doctree b/docs/1.1/.doctrees/user_manual/config.doctree new file mode 100644 index 00000000..0dda8b2d Binary files /dev/null and b/docs/1.1/.doctrees/user_manual/config.doctree differ diff --git a/docs/1.1/.doctrees/user_manual/factory.doctree b/docs/1.1/.doctrees/user_manual/factory.doctree new file mode 100644 index 00000000..356be183 Binary files /dev/null and b/docs/1.1/.doctrees/user_manual/factory.doctree differ diff --git a/docs/1.1/.doctrees/user_manual/io.doctree b/docs/1.1/.doctrees/user_manual/io.doctree new file mode 100644 index 00000000..e017da2d Binary files /dev/null and b/docs/1.1/.doctrees/user_manual/io.doctree differ diff --git a/docs/1.1/.doctrees/user_manual/transfer.doctree b/docs/1.1/.doctrees/user_manual/transfer.doctree new file mode 100644 index 00000000..096345f7 Binary files /dev/null and b/docs/1.1/.doctrees/user_manual/transfer.doctree differ diff --git a/docs/1.1/.doctrees/user_manual/user_manual.doctree b/docs/1.1/.doctrees/user_manual/user_manual.doctree new file mode 100644 index 00000000..c9a6900a Binary files /dev/null and b/docs/1.1/.doctrees/user_manual/user_manual.doctree differ diff --git a/docs/1.1/.nojekyll b/docs/1.1/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/1.1/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns b/docs/1.1/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns new file mode 100644 index 00000000..5f3f4e8a Binary files /dev/null and b/docs/1.1/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns differ diff --git a/docs/1.1/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns b/docs/1.1/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns new file mode 100644 index 00000000..92982a93 Binary files /dev/null and b/docs/1.1/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns differ diff --git a/docs/1.1/_images/data_dist.svg b/docs/1.1/_images/data_dist.svg new file mode 100644 index 00000000..5161b893 --- /dev/null +++ b/docs/1.1/_images/data_dist.svg @@ -0,0 +1,265 @@ + + + +(a)(b) diff --git a/docs/1.1/_images/data_dist_gnum.svg b/docs/1.1/_images/data_dist_gnum.svg new file mode 100644 index 00000000..0270c247 --- /dev/null +++ b/docs/1.1/_images/data_dist_gnum.svg @@ -0,0 +1,229 @@ + + + +dist=[0,3,5,7] diff --git a/docs/1.1/_images/data_full.svg b/docs/1.1/_images/data_full.svg new file mode 100644 index 00000000..773e4861 --- /dev/null +++ b/docs/1.1/_images/data_full.svg @@ -0,0 +1,161 @@ + + + +1234567 diff --git a/docs/1.1/_images/data_part.svg b/docs/1.1/_images/data_part.svg new file mode 100644 index 00000000..702ee2cc --- /dev/null +++ b/docs/1.1/_images/data_part.svg @@ -0,0 +1,307 @@ + + + +(a)(b)(c) diff --git a/docs/1.1/_images/data_part_gnum.svg b/docs/1.1/_images/data_part_gnum.svg new file mode 100644 index 00000000..5f8bad52 --- /dev/null +++ b/docs/1.1/_images/data_part_gnum.svg @@ -0,0 +1,252 @@ + + + +LN_to_GN=[1,6,4,3]LN_to_GN=[2,7,5]LN_to_GN=[3,5] diff --git a/docs/1.1/_images/dist_mesh.svg b/docs/1.1/_images/dist_mesh.svg new file mode 100644 index 00000000..cab59cbc --- /dev/null +++ b/docs/1.1/_images/dist_mesh.svg @@ -0,0 +1,491 @@ + + + +image/svg+xml1 +4 +3 +2 +6 +5 +1 +9 +8 +7 +2 +10 +11 +6 +3 +12 +4 +5 + \ No newline at end of file diff --git a/docs/1.1/_images/dist_mesh_arrays.svg b/docs/1.1/_images/dist_mesh_arrays.svg new file mode 100644 index 00000000..99abc251 --- /dev/null +++ b/docs/1.1/_images/dist_mesh_arrays.svg @@ -0,0 +1,1073 @@ + + + +image/svg+xmlCoordinateX +CoordinateY +Connectivity +1 +2 +3 +4 +5 +6 +1 2 3 4 5 6 7 8 9 +10 +11 +12 +1 9 +10 +2 +2 +10 +12 +3 8 7 6 +11 +9 8 +111011 +6 5 4 +10 +11 +4 +12 + diff --git a/docs/1.1/_images/dist_part_LN_to_GN.svg b/docs/1.1/_images/dist_part_LN_to_GN.svg new file mode 100644 index 00000000..91c01db2 --- /dev/null +++ b/docs/1.1/_images/dist_part_LN_to_GN.svg @@ -0,0 +1,2169 @@ + + + +image/svg+xml1 +4 +3 +2 +6 +5 +1 +9 +8 +7 +2 +10 +11 +6 +3 +12 +4 +5 +8 +7 +6 +5 +4 +1 +2 +3 +6 +7 +8 +3 +4 +5 +1 +2 +2 +1 +3 +1 +2 +3 +LN to GN of +vertices +LN +to +GN of +elements +1 2 3 4 5 6 7 8 +1 2 3 4 5 6 7 8 +2 1 6 +3 +12 +4 +11 +10 +2 9 1 +4 5 +10 +11 +6 9 8 7 +4 3 5 +1 +2 +3 +1 +2 +3 +Distributed +mesh +Sub +- +mesh +0 +Sub +- +mesh +1 +Sub +- +mesh +0 +Sub +- +mesh +1 + \ No newline at end of file diff --git a/docs/1.1/_images/dist_tree.png b/docs/1.1/_images/dist_tree.png new file mode 100644 index 00000000..d62c4cb4 Binary files /dev/null and b/docs/1.1/_images/dist_tree.png differ diff --git a/docs/1.1/_images/dist_tree_expl.png b/docs/1.1/_images/dist_tree_expl.png new file mode 100644 index 00000000..ae830142 Binary files /dev/null and b/docs/1.1/_images/dist_tree_expl.png differ diff --git a/docs/1.1/_images/full_mesh.svg b/docs/1.1/_images/full_mesh.svg new file mode 100644 index 00000000..6dc266fe --- /dev/null +++ b/docs/1.1/_images/full_mesh.svg @@ -0,0 +1,509 @@ + + + +image/svg+xml1 + +4 + +3 + +2 + +6 + +5 + +1 + +9 + +8 + +7 + +2 + +10 + +11 + +6 + +3 + +12 + +4 + +5 + + \ No newline at end of file diff --git a/docs/1.1/_images/part_mesh.svg b/docs/1.1/_images/part_mesh.svg new file mode 100644 index 00000000..a008bd94 --- /dev/null +++ b/docs/1.1/_images/part_mesh.svg @@ -0,0 +1,598 @@ + + + +image/svg+xml8 +7 +6 +5 +4 +1 +2 +3 +6 +7 +8 +3 +4 +5 +1 +2 +2 +1 +3 +1 +2 +3 + \ No newline at end of file diff --git a/docs/1.1/_images/part_mesh_arrays.svg b/docs/1.1/_images/part_mesh_arrays.svg new file mode 100644 index 00000000..110edd78 --- /dev/null +++ b/docs/1.1/_images/part_mesh_arrays.svg @@ -0,0 +1,888 @@ + + + +image/svg+xmlCoordinateX +CoordinateY +Connectivity +1 +2 +3 +1 +2 +3 +1 2 3 4 5 6 7 8 +1 2 3 4 5 6 7 8 +6 5 2 1 8 7 5 6 +5 4 3 2 +6 7 4 3 7 8 5 4 +4 5 2 1 + diff --git a/docs/1.1/_images/part_tree.png b/docs/1.1/_images/part_tree.png new file mode 100644 index 00000000..eced07ef Binary files /dev/null and b/docs/1.1/_images/part_tree.png differ diff --git a/docs/1.1/_images/part_tree_expl.png b/docs/1.1/_images/part_tree_expl.png new file mode 100644 index 00000000..843ae4e3 Binary files /dev/null and b/docs/1.1/_images/part_tree_expl.png differ diff --git a/docs/1.1/_images/qs_basic.png b/docs/1.1/_images/qs_basic.png new file mode 100644 index 00000000..b17d8907 Binary files /dev/null and b/docs/1.1/_images/qs_basic.png differ diff --git a/docs/1.1/_images/qs_pycgns.png b/docs/1.1/_images/qs_pycgns.png new file mode 100644 index 00000000..54176820 Binary files /dev/null and b/docs/1.1/_images/qs_pycgns.png differ diff --git a/docs/1.1/_images/qs_workflow.png b/docs/1.1/_images/qs_workflow.png new file mode 100644 index 00000000..e4b26743 Binary files /dev/null and b/docs/1.1/_images/qs_workflow.png differ diff --git a/docs/1.1/_images/tree_seq.png b/docs/1.1/_images/tree_seq.png new file mode 100644 index 00000000..9fcff12c Binary files /dev/null and b/docs/1.1/_images/tree_seq.png differ diff --git a/docs/1.1/_images/workflow.svg b/docs/1.1/_images/workflow.svg new file mode 100644 index 00000000..dd21687a --- /dev/null +++ b/docs/1.1/_images/workflow.svg @@ -0,0 +1,669 @@ + + + +image/svg+xmldist_tree +Field +transfers +Distributed +algorithms +zone +merges +, +connectivity +transformations… +Partitioned +algorithms +Solver +, +wall +distances, +HPC +renumbering + +Part_tree +Part_tree +part_tree +Creation +by +partitioning +CGNS +File + \ No newline at end of file diff --git a/docs/1.1/_sources/developer_manual/algo_description.rst.txt b/docs/1.1/_sources/developer_manual/algo_description.rst.txt new file mode 100644 index 00000000..1e5eb7a7 --- /dev/null +++ b/docs/1.1/_sources/developer_manual/algo_description.rst.txt @@ -0,0 +1,10 @@ +Algorithms description +====================== + +This section provides a detailed description of some algorithms. + +.. toctree:: + :maxdepth: 1 + + algo_description/elements_to_ngons.rst + diff --git a/docs/1.1/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt b/docs/1.1/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt new file mode 100644 index 00000000..4827afef --- /dev/null +++ b/docs/1.1/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt @@ -0,0 +1,230 @@ +.. _elements_to_ngons_impl: + +elements_to_ngons +================= + +Description +----------- + +.. code-block:: python + + maia.algo.dist.elements_to_ngons(dist_tree_elts, comm) + +Take a **distributed** :cgns:`CGNSTree_t` or :cgns:`CGNSBase_t`, and transform it into a **distributed** :cgns:`NGon/NFace` mesh. The tree is modified in-place. + +Example +------- + +.. literalinclude:: ../../user_manual/snippets/test_algo.py + :start-after: #elements_to_ngons@start + :end-before: #elements_to_ngons@end + :dedent: 2 + +Arguments +--------- + +:code:`dist_tree_elts` + a :ref:`distributed tree ` with + * unstructured zones that contain regular element sections (i.e. not NGON, not NFACE and not MIXED) + * the :ref:`Maia tree ` property + +:code:`comm` + a communicator over which :code:`elements_to_ngons` is called + +Output +------ + +The tree is modified in-place. The regular element sections are replaced by: + +* a NGon section with: + + * An :cgns:`ElementStartOffset`/:cgns:`ElementConnectivity` node describing: + + * first the external faces in exactly the same order as they were (in particular, gathered by face type) + * then the internal faces, also gathered by face type + + * a :cgns:`ParentElements` node and a :cgns:`ParentElementsPosition` node + +* a NFace section with cells ordered as they were, with ids only shifted by the number of interior faces + + +Parallelism +----------- + +:code:`elements_to_ngons` is a collective function. + +Complexity +---------- + +With :math:`N` the number of zones, :math:`n_i` the number of elements of zone :math:`i`, :math:`n_{f,i}` its number of interior faces, and :math:`K` the number of processes + +Sequential time + :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i \cdot log(n_i) \right)` + + The cost is dominated by the sorting of faces (interior and exterior) used to spot identical ones. + +Parallel time + :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i/K \cdot log(n_i) \right)` + + The parallel distributed sort algorithm consists of three steps: + + 1. A partitioning step that locally gather faces into :math:`K` buckets that are of equal global size. This uses a :math:`K`-generalized variant of `quickselect `_ that is of complexity :math:`\displaystyle \mathcal{O} \left( n_i/K \cdot log(K) \right)` + 2. An all_to_all exchange step that gather the :math:`K` buckets on the :math:`K` processes. This step is not accounted for here (see below) + 3. A local sorting step that is :math:`\displaystyle \mathcal{O} \left( n_i/K \cdot log(n_i/K) \right)` + + If we sum up step 1 and 3, we get + +.. math:: + + \begin{equation} \label{eq1} + \begin{split} + \textrm{C}_\textrm{parallel_sort} & = \mathcal{O} \left( n_i/K \cdot log(K) + n_i/K \cdot log(n_i/K) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot \left( log(K) + log(n_i/K) \right) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot log(n_i) \right) + \end{split} + \end{equation} + +Theoretical scaling + :math:`\textrm{Speedup} = K` + + Experimentally, the scaling is much worse - under investigation. + + Note: the speedup is computed by :math:`\textrm{Speedup} = t_s / t_p` where :math:`t_s` is the sequential time and :math:`t_p` the parallel time. A speedup of :math:`K` is perfect, a speedup lower than :math:`1` means that sequential execution is faster. + +Peak memory + Approximately :math:`\displaystyle \sum_{i=0}^N 2 n_i + n_{f,i}` + + This is the size of the input tree + the output tree. :math:`n_i` is counted twice: once for the input element connectivity, once for the output NFace connectivity + +Size of communications + Approximately :math:`\displaystyle \sum_{i=0}^N 3 n_{f,i} + n_i` **all_to_all** calls + + For each zone, one **all_to_all** call to sort interior faces, one to send back the faces to the NFace, one to concatenate all faces, one to concatenate all cells + +Number of communication calls + Should be :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N log(n_i/K) \right)` + + The number of communications is constant, except for the algorithm finding a balanced distribution of interior faces + +Note + In practice, :math:`n_{f,i}` varies from :math:`2 n_i` (tet-dominant meshes) to :math:`3 n_i` (hex-dominant meshes). + +Algorithm explanation +--------------------- + +.. code-block:: c++ + + maia::elements_to_ngons(cgns::tree& dist_zone, MPI_Comm comm) + +The algorithm is divided in two steps: + +1. Generate interior faces, insert them between exterior faces and cells. During the process, also compute the parent information (:cgns:`ParentElements` and :cgns:`ParentElementsPosition`) and add the :cgns:`CellFace` connectivity to cells. All elements keep their standard element types, in particular interior faces are inserted by section type (e.g. there will be a :cgns:`TRI_3_interior` node and a :cgns:`QUAD_4_interior` node, not a :cgns:`NGon` node) +2. Transform all sections into :cgns:`NGon/NFace` + +Generate interior faces +^^^^^^^^^^^^^^^^^^^^^^^ + +Simplified algorithm +"""""""""""""""""""" + +Let us look at a simplified sequential algorithm first: + +.. code-block:: text + + 1. For all element sections (3D and 2D): + Generate faces + => FaceVtx arrays (one for Tris, one for Quads) + => Associated Parent and ParentPosition arrays + (only one parent by element at this stage) + Interior faces are generated twice (by their two parent cells) + Exterior faces are generated twice (by a parent cell and a boundary face) + + 2. For each element kind in {Tri,Quad} + Sort the FaceVtx array + (ordering: lexicographical comparison of vertices) + Sort the parent arrays accordingly + => now each face appears consecutively exactly twice + for interior faces, + the FaceVtx is always inverted between the two occurences + for exterior faces, + it depends on the normal convention + + Note that interior and exterior faces can be distinguised + by looking at the id of their parents + + 3. For each interior face appearing twice: + Create an interior face section where: + the first FaceVtx is kept + the two parents are stored + 4. For each exterior face appearing twice: + One of the face is the original boundary section face, + one was generated from the joint cell + Send back to the original boundary face its parent face id and position + => store the parent information of the boundary face + +Parallel algorithm +"""""""""""""""""" + +The algorithm is very similar to the sequential one. We need to modify two operations: + +Sorting of the FaceVtx array (step 2) + The parallel sorting is done in three steps: + + 1. apply a partial sort :cpp:`std_e::sort_by_rank` that will determine the rank of each FaceVtx + 2. call an :cpp:`all_to_all` communication step that sends each connectivity to its rank, based on the information of the previous step + 3. sort each received FaceVtx locally + +Send back boundary parents and position to the original boundary faces (step 4) + Since the original face can be remote, this is a parallel communication operation using :cpp:`std_e::scatter` + +Computation of the CellFace +""""""""""""""""""""""""""" + +After step 2, we have all the faces exactly once, with their parent ids and parent positions. +We can then compute the CellFace of each cell section by the following algorithm: + +.. code-block:: text + + For each cell section: + pre-allocate the CellFace array + (its size is n_cell_in_section * n_face_of_cell_type) + view it as a global distributed array + For each unique face: + For each of its parent cells (could be one or two): + send the parent cell the id of the face and its position + insert the result in the CellFace array + +As previously, the send operation uses a **scatter** pattern + +Transform all sections into NGon/NFace +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Thanks to the previous algorithm, we have: + +* all exterior and interior faces with their parent information +* the CellFace connectivity of the cell sections + +Elements are ordered in something akin to this: + +* boundary tris +* boundary quads +* internal tris +* internal quads + +* tetras +* pyras +* prisms +* hexas + +The algorithm then just needs to: + +* concatenate all FaceVtx of the faces into a :cgns:`NGon` node and add a :cgns:`ElementStartOffset` +* concatenate all CellFace of the cells into a :cgns:`NFace` node and add a :cgns:`ElementStartOffset` + +Design alternatives +------------------- + +The final step of the computation involves concatenating faces and cells global section by global section. This requires two heavyweight **all_to_all** calls. An alternative would be to concatenate locally. This would imply two trade-offs: + +* the faces and cells would then not be globally gathered by type, and the exterior faces would not be first +* all the :cgns:`PointLists` (including those where :cgns:`GridLocation=FaceCenter`) would have to be shifted diff --git a/docs/1.1/_sources/developer_manual/developer_manual.rst.txt b/docs/1.1/_sources/developer_manual/developer_manual.rst.txt new file mode 100644 index 00000000..ab887981 --- /dev/null +++ b/docs/1.1/_sources/developer_manual/developer_manual.rst.txt @@ -0,0 +1,13 @@ +.. _dev_manual: + +################ +Developer Manual +################ + +.. toctree:: + :maxdepth: 1 + + logging + maia_dev/conventions + maia_dev/development_workflow + algo_description diff --git a/docs/1.1/_sources/developer_manual/logging.rst.txt b/docs/1.1/_sources/developer_manual/logging.rst.txt new file mode 100644 index 00000000..9592d08f --- /dev/null +++ b/docs/1.1/_sources/developer_manual/logging.rst.txt @@ -0,0 +1,122 @@ +.. _logging: + +Log management +============== + + +Loggers +------- + +A **logger** is a global object where an application or a library can log to. +It can be declared with + +.. literalinclude:: snippets/test_logging.py + :start-after: #add_logger@start + :end-before: #add_logger@end + :dedent: 2 + +or with the equivalent C++ code + +.. code-block:: C++ + + #include "std_e/logging/log.hpp" + + std_e::add_logger("my_logger"); + +A logger declared in Python is available in C++ and vice-versa: we do not need to create it twice. + +.. note:: Loggers are available to pure C/C++ programs (in particular, test programs that do not use the Python interpreter). + +It can then be referred to by its name. If we want to log a string to ``my_logger``, we will do it like so: + +.. literalinclude:: snippets/test_logging.py + :start-after: #log@start + :end-before: #log@end + :dedent: 2 + +.. code-block:: C++ + + #include "std_e/logging/log.hpp" + + std_e::log("my_logger", "my message"); + +Typically, an application will use several loggers to log messages by themes. By convention, the name of a logger in the application ``my_app`` should begin with ``my_app``. For instance, loggers can be named: ``my_app``, ``my_app-stats``, ``my_app-errors``... + +Loggers are both + +- developper-oriented, that is, when a developper wants to output a message, he should select a logger (existing or not) encompassing the theme of the message he wants to log, +- user-oriented, because a user can choose what logger he wants to listen to. + + + +Printers +-------- + +By itself, a logger does not do anything with the messages it receives. For that, we need to attach **printers** to a logger that will handle its messages. + +For instance, we can attach a printer that will output the message to the console: + +.. literalinclude:: snippets/test_logging.py + :start-after: #add_printer@start + :end-before: #add_printer@end + :dedent: 2 + +Printers are user-oriented: it is the user of the application who decides what he wants to do with the message that each logger is receiving, by attaching none or several printers to a logger. + +Available printers +^^^^^^^^^^^^^^^^^^ + +stdout_printer, stderr_printer + output messages to the console (respectively stdout and stderr) + +mpi_stdout_printer, mpi_stderr_printer + output messages to the console, but prefix them by ``MPI.COMM_WORLD.Get_rank()`` + +mpi_rank_0_stdout_printer, mpi_rank_0_stderr_printer + output messages to the console, if ``MPI.COMM_WORLD.Get_rank()==0`` + +file_printer('my_file.extension') + output messages to file ``my_file.extension`` + +mpi_file_printer('my_file.extension') + output messages to files ``my_file.{rk}.extension``, with ``rk = MPI.COMM_WORLD.Get_rank()`` + +.. note:: + MPI-aware printers use ``MPI.COMM_WORLD`` rather than a local communicator. While the latter would be more correct, it would imply local instanciation of printers, at the time or after a new communicator is created. While this is certainly doable, up until now we feel that it does not worth the burden. But if the need arises, they can still be added. + +Create your own printer +^^^^^^^^^^^^^^^^^^^^^^^ + +Any Python type can be used as a printer as long as it provides a ``log`` method that accepts a string argument. + +.. literalinclude:: snippets/test_logging.py + :start-after: #create_printer@start + :end-before: #create_printer@end + :dedent: 2 + + +Configuration file +------------------ + +Loggers are associated to default printers. While they can be configured anytime in the Python scripts, most of the time, reading a configuration file at the start of the program is enough. The program will try to read a configuration file if the environment variable ``LOGGING_CONF_FILE`` is set. A logging configuration file looks like this: + + +.. code-block:: Text + + my_app : mpi_stdout_printer + my_app-my_theme : mpi_file_printer('my_theme.log') + +For developpers, a logging file ``logging.conf`` with loggers and default printers is put in the ``build/`` folder, and ``LOGGING_CONF_FILE`` is set accordingly. + +Maia specifics +-------------- + +Maia provides 4 convenience functions that use Maia loggers + +.. code-block:: python + + from maia.utils import logging as mlog + mlog.info('info msg') # uses the 'maia' logger + mlog.stat('stat msg') # uses the 'maia-stats' logger + mlog.warning('warn msg') # uses the 'maia-warnings' logger + mlog.error('error msg') # uses the 'maia-errors' logger diff --git a/docs/1.1/_sources/developer_manual/maia_dev/conventions.rst.txt b/docs/1.1/_sources/developer_manual/maia_dev/conventions.rst.txt new file mode 100644 index 00000000..f171f99a --- /dev/null +++ b/docs/1.1/_sources/developer_manual/maia_dev/conventions.rst.txt @@ -0,0 +1,29 @@ +Conventions +=========== + +Naming conventions +------------------ + +* **snake_case** +* A variable holding a number of things is written :code:`n_thing`. Example: :code:`n_proc`, :code:`n_vtx`. The suffix is singular. +* For unit tests, when testing variable :code:``, the hard-coded expected variable is named :code:`expected_`. +* Usual abbreviations + + * **elt** for **element** + * **vtx** for **vertex** + * **proc** for **process** + * **sz** for **size** (only for local variable names, not functions) + +* Connectivities + + * **cell_vtx** means mesh array of cells described by their vertices (CGNS example: :cgns:`HEXA_8`) + * **cell_face** means the cells described by their faces (CGNS example: :cgns:`NFACE_n`) + * **face_cell** means for each face, the two parent cells (CGNS example: :cgns:`ParentElement`) + * ... so on: **face_vtx**, **edge_vtx**... + * **elt_vtx**, **elt_face**...: in this case, **elt** can be either a cell, a face or an edge + + +Other conventions +----------------- + +We try to use semi-open intervals and 0-indexed structures. This is coherent with Python, C++, and their libraries, but unfortunately not with CGNS. diff --git a/docs/1.1/_sources/developer_manual/maia_dev/development_workflow.rst.txt b/docs/1.1/_sources/developer_manual/maia_dev/development_workflow.rst.txt new file mode 100644 index 00000000..96231c45 --- /dev/null +++ b/docs/1.1/_sources/developer_manual/maia_dev/development_workflow.rst.txt @@ -0,0 +1,34 @@ +Development workflow +==================== + +Sub-modules +----------- + +The **Maia** repository is compatible with the development process described `here `_. It uses git submodules to ease the joint development with other repositories compatible with this organization. + +TL;DR: configure the git repository by sourcing `this file `_ and then execute: + +.. code-block:: bash + + cd $MAIA_FOLDER + git submodule update --init + git_config_submodules + + +Launch tests +------------ + +Tests can be launched by calling CTest, but during the development, we often want to parameterize how to run tests (which ones, number of processes, verbosity level...). + +There is a :code:`source.sh` generated in the :code:`build/` folder. It can be sourced in order to get the correct environment to launch the tests (notably, it updates :code:`LD_LIBRARY_PATH` and :code:`PYTHONPATH` to point to build artifacts). + +Tests can be called with e.g.: + +.. code:: bash + + cd $PROJECT_BUILD_DIR + source source.sh + mpirun -np 4 external/std_e/std_e_unit_tests + ./external/cpp_cgns/cpp_cgns_unit_tests + mpirun -np 4 test/maia_doctest_unit_tests + mpirun -np 4 pytest $PROJECT_SRC_DIR/maia --with-mpi diff --git a/docs/1.1/_sources/index.rst.txt b/docs/1.1/_sources/index.rst.txt new file mode 100644 index 00000000..ee2e8a36 --- /dev/null +++ b/docs/1.1/_sources/index.rst.txt @@ -0,0 +1,42 @@ +**************** +Welcome to Maia! +**************** + +**Maia** is a C++ and Python library for parallel algorithms over CGNS trees: distributed IO, partitioning, data management and various kind of transformations (generation of faces, destructuration, extractions, ...). + +Maia is an open source software developed at `ONERA `_. +Associated source repository and issue tracking are hosted on `Gitlab `_. + +!!!! + +Documentation summary +--------------------- + +:ref:`Quick start ` is the perfect page for a first overview of Maia or to retrieve the environments to use on Onera's clusters. + +:ref:`Introduction ` details the extensions made to the CGNS standard in order to define parallel CGNS trees. + +:ref:`User Manual ` is the main part of this documentation. It describes most of the high level APIs provided by Maia. + +:ref:`Developer Manual ` (under construction) provides more details on some algorithms and can be consulted if you want to contribute to Maia. + + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Reference + + quick_start + installation + introduction/introduction + user_manual/user_manual + developer_manual/developer_manual + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Appendix + + releases/release_notes + related_projects + license diff --git a/docs/1.1/_sources/installation.rst.txt b/docs/1.1/_sources/installation.rst.txt new file mode 100644 index 00000000..d0a8ffcc --- /dev/null +++ b/docs/1.1/_sources/installation.rst.txt @@ -0,0 +1,173 @@ +.. _installation: + +Installation +############ + +Prefered installation procedure +=============================== + +Maia depends on quite a few libraries of different kinds, be it system libraries like MPI, third-party libraries like HDF5, ONERA libraries like ParaDiGM and Cassiopée, git submodules (std_e...), or Python modules (mpi4py, ruamel). The prefered way of installing Maia in a coherent environment is by using the `Spack package manager `_. A Spack recipe for Maia can be found on the `ONERA Spack repository `_. + +Installation through Spack +-------------------------- + +1. Source a Spack repository on your machine. +2. If you don't have a Spack repository ready, you can download one with :code:`git clone https://github.com/spack/spack.git`. On ONERA machines, it is advised to use the `Spacky `_ helper. +3. Download the **ONERA Spack repository** with :code:`git clone https://gitlab.onera.net/informatics/infra/onera_spack_repo.git` +4. Tell Spack that package recipes are in :code:`onera_spack_repo` by adding the following lines to :code:`$SPACK_ROOT/etc/repos.yaml`: + +.. code-block:: yaml + + repos: + - path/to/onera_spack_repo + +(note that **spacky** does steps 3. and 4. for you) + +5. You should be able to see the package options of Maia with :code:`spack info maia` +6. To install Maia: :code:`spack install maia` + + +Development workflow +-------------------- + +For development, it is advised to use Spack to have Maia dependencies, but then follow a typical CMake workflow with :code:`cmake/make`. + + +Dependencies +^^^^^^^^^^^^ + +To get access to Maia dependencies in your development environment, you can: + +* Install a Spack version of Maia, source it in your development environment to get all the dependencies, then override with your own compiled version of Maia +* Do the same, but use a Spack environment containing Maia instead of just the Maia package +* Source a Spack environment where Maia has been removed from the environment view. This can be done by adding the following lines to the :code:`spack.yaml` environement file: + +.. code-block:: yaml + + view: + default: + exclude: ['maia'] + +This last option is cleaner because you are sure that you are not using another version of Maia (but it means you need to create or have access to such an environment view) + +Source the build folder +^^^^^^^^^^^^^^^^^^^^^^^ + +You can develop without the need to install Maia. However, in addition to sourcing your dependencies in your development environment, you also need to source the build artifacts by: + +.. code-block:: bash + + cd $MAIA_BUILD_FOLDER + source source.sh + +The :code:`source.sh` file is created by CMake and will source all Maia artifacts (dynamic libraries, python modules...) + + +Development workflow with submodules +------------------------------------ + +It is often practical to develop Maia with some of its dependencies, namely: + +* project_utils +* std_e +* cpp_cgns +* paradigm +* pytest_parallel + +For that, you need to use git submodules. Maia submodules are located at :code:`$MAIA_FOLDER/external`. To populate them, use :code:`git submodule update --init`. Once done, CMake will use these versions of the dependencies. If you don't populate the submodules, CMake will try to use the ones of your environment (for instance, the one installed by Spack). + +We advise that you use some additional submodule configuration utilities provided in `this file `_. In particular, you should use: + +.. code-block:: bash + + cd $MAIA_FOLDER + git submodule update --init + git_config_submodules + +The detailed meaning of `git_config_submodules` and the git submodule developper workflow of Maia is presented `here `_. + +If you are using Maia submodules, you can filter them out from your Spack environment view like so: + +.. code-block:: yaml + + view: + default: + exclude: ['maia','std-e','cpp-cgns','paradigm','pytest_parallel'] + +Manual installation procedure +============================= + +Dependencies +------------ + +**Maia** depends on: + +* python3 +* MPI +* hdf5 + +* Cassiopée + +* pytest >6 (python package) +* ruamel (python package) +* mpi4py (python package) + +The build process requires: + +* Cmake >= 3.14 +* GCC >= 8 (Clang and Intel should work but no CI) + + +Other dependencies +^^^^^^^^^^^^^^^^^^ + +During the build process, several other libraries will be downloaded: + +* pybind11 +* range-v3 +* doctest + +* ParaDiGM +* project_utils +* std_e +* cpp_cgns + +This process should be transparent. + + +Optional dependencies +^^^^^^^^^^^^^^^^^^^^^ + +The documentation build requires: + +* Doxygen >= 1.8.19 +* Breathe >= 4.15 (python package) +* Sphinx >= 3.00 (python package) + +Build and install +----------------- + +1. Install the required dependencies. They must be in your environment (:code:`PATH`, :code:`LD_LIBRARY_PATH`, :code:`PYTHONPATH`). + + For pytest, you may need these lines : + +.. code:: bash + + pip3 install --user pytest + pip3 install --user pytest-mpi + pip3 install --user pytest-html + pip3 install --user pytest_check + pip3 install --user ruamel.yaml + +2. Then you need to populate your :code:`external` folder. You can do it with :code:`git submodule update --init` + +3. Then use CMake to build maia, e.g. + +.. code:: bash + + SRC_DIR= + BUILD_DIR= + INSTALL_DIR= + cmake -S $SRC_DIR -B$BUILD_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR + cd $BUILD_DIR && make -j && make install + diff --git a/docs/1.1/_sources/introduction/introduction.rst.txt b/docs/1.1/_sources/introduction/introduction.rst.txt new file mode 100644 index 00000000..29f36576 --- /dev/null +++ b/docs/1.1/_sources/introduction/introduction.rst.txt @@ -0,0 +1,302 @@ +.. _intro: + +Introduction +============ + +These section introduces the core concepts of distributed and partitioned data +used in Maia, and their application to define parallel CGNS trees. + +Core concepts +------------- + +Dividing data +^^^^^^^^^^^^^ + +:def:`Global data` is the complete data that describes an object. Let's represent it as the +following ordered shapes: + +.. image:: ./images/dist_part/data_full.svg + +Now imagine that you want to split this data into N pieces (N=3 for the next illustrations). +Among all the possibilities, we distinguishes two ways to do it: + +1. Preserving order: we call such repartition :def:`distributed data`, and we use the term :def:`block` + to refer to a piece of this distributed data. + + .. image:: ./images/dist_part/data_dist.svg + + Several distributions are possible, depending on where data is cut, but they all share the same properties: + + - the original order is preserved across the distributed data, + - each element appears in one and only one block, + - a block can be empty as long as the global order is preserved (b). + +2. Taking arbitrary subsets of the original data: we call such subsets :def:`partitioned data`, and we use the term :def:`partition` + to refer to a piece of this partitioned data. + + .. image:: ./images/dist_part/data_part.svg + + Due to this relaxed constraint, there is much more admissible splits since we allow in our definition the following cases: + + - an element can appear in several partitions, or several times within the same partition (b), + - it is allowed that an element does not appear in a partition (c). + + Such repartitions are often useful when trying to gather the elements depending on + some characteristics: on the above example, we created the partition of squared shaped elements, round shaped + elements and unfilled elements (b). Thus, some elements belong to more than one partition. + +A key point is that no *absolute* best way of diving data: depending of what we want to do with the +data, one of the way to divide it may be more adapted. In the previous example: + +- distributed data is fine if you want to count the number of filled shapes: you can count in each + block and then sum the result over the blocks. +- Now assume that you want to renumber the elements depending on their shape, then on their color: + if partitioned data (b) is used, partitions 1 and 2 could independently order + their elements by color since they are already sorted by shape [#f1]_. + +Numberings +^^^^^^^^^^ + +In order to describe the link between our divisions and the original global data, we need to +define additional concepts. + +For distributed data, since the original ordering is respected, the link with the global data is totally implicit: +we just need to know the number of elements in each block or, equivalently, the :def:`distribution array` +of the data. This is an array of size :mono:`N+1` indicating the bounds of each block. By convention, distribution +array starts at 0 and uses semi-open intervals. + + +.. image:: ./images/dist_part/data_dist_gnum.svg + +With this information, the global number of the jth element in the ith block is given by +:math:`\mathtt{dist[i] + j + 1}`. + +On the contrary, for partitioned data, we have to explicitly store the link with the global data: +we use a :def:`local to global numbering array` (often called :mono:`LN_to_GN` for short). +Each partition has its own :mono:`LN_to_GN` array whose size is the number of elements in the partition. + +.. image:: ./images/dist_part/data_part_gnum.svg + +Then, the global number of the jth element in the ith partition is simply given by +:math:`\mathtt{LN\_to\_GN[i][j]}`. + +For any global data, these additional informations allow to create a mapping beetween global data, partitioned +data and distributed data. Thus, it is always possible to reconstruct one of the views from another one. + +Application to MPI parallelism +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The concepts introduced above make all sense in the context of distributed memory computers. +In such architecture, the global data is in fact never detained by a single proc (it would be too heavy): +we always use a distributed or partitioned view of this data. +Using a good repartition is often the key of a well balanced algorithm. + +In the distributed view, we produce as much blocks as the number of MPI processes. Each process holds +its own block, and the distribution array, of size :mono:`n_rank+1`, is know by each process. + +In the partitioned view, we often produce one partition per process; but it can sometime be useful to +put several partitions on a given process. Each process holds the data associated to its partition, +including the related :mono:`LN\_to\_GN` arrays (:mono:`LN\_to\_GN` related to the other partitions +are not know by the current process). + +The :ref:`ParaDiGM ` library provides some low level methods to exchange data between the partitioned and +distributed views in a MPI context, with options to deal with redundant elements, create new distributions, +manage variable strides, etc. + + +Application to meshes +--------------------- + +Until now, we have seen the definition of distributed and partitioned data for a single array +of global data. Let see how to apply these concepts to a basic CFD mesh. + +Consider the following 12-vertices (green numbers), 6-quads (purple numbers) mesh and +assume that it is described by a cell to vertex connectivity. Thus, the mesh file defines +3 global arrays: + +- the CoordinateX and CoordinateY arrays, each one of size 12 +- the Connectivity array of size 6*4 = 24 + +.. image:: ./images/dist_part/full_mesh.svg + +If we have 2 processes at our disposal, a parallel way to load this mesh is to +distribute all the vertex-related entities with a **distribution array** of :code:`[0,6,12]` +and all the element-related entities with a distribution array of :code:`[0,3,6]` [#f2]_: + +.. image:: ./images/dist_part/dist_mesh_arrays.svg + +Then, the blue part of the arrays will be stored on the first process and the red part on the second process. +Looking back at the original mesh, this correspond to the following distributed meshes: + +.. image:: ./images/dist_part/dist_mesh.svg + +with the blue entities stored on the first process, and the red ones on the second process. + + +Notice that the distributed mesh is not suited for solver computation. For instance, cells on the domain treated by the first process are not connex. Worst yet, vertex and element quantities are distributed independently. Take a look at cell 1: it is blue so it belongs to process 0. However,its vertices 9 and 10 are on process 1, this means that there coordinates are not accessible directly on process 0. For these reasons, we want to properly partition our mesh, such as this: + +.. image:: ./images/dist_part/part_mesh.svg + +.. image:: ./images/dist_part/part_mesh_arrays.svg + +Now we have two semi-independent meshes and we can reason about each element with all its associated data +present on the same process. This partitioned view of the mesh has the two following properties: + + - Coherency: every data array is addressable locally, + - Connexity: the data represents geometrical entities that define a local subregion of the mesh. + +We want to keep the link between the base mesh and its partitioned version. For that, we need to store :def:`global numbering arrays`, quantity by quantity: + +.. image:: ./images/dist_part/dist_part_LN_to_GN.svg + +For example, for sub-mesh 0, we can check that element number 3 is actually element number 6 of the original mesh. +We can also see that vertex 3 of sub-mesh 0, and vertex 1 of submesh 1 are actually the same, that is, vertex 4 of the global mesh. + +Thanks to these global numbering, any quantity computed on the partitioned meshes (such as a vertex based field) +can then be transfered to the corresponding distributed vertex, before writting back the results. + +Maia CGNS trees +--------------- + +Overview +^^^^^^^^ + +Since Python/CGNS trees describe CFD meshes, we can apply the previous concepts to +define parallel trees. + +A :def:`full tree` is a tree as it is inside a CGNS file, or how it would be loaded by only one process. A full tree is **global data**. + +A :def:`dist tree` is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. See :ref:`dist_tree`. + +A :def:`part tree` is a partial tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. See :ref:`part_tree`. + +A :def:`size tree` is a tree in which only the size of the data is stored. A *size tree* is typically *global data* because each process needs it to know which *block* of data it will have to load and store. + +([Legacy] A :def:`skeleton tree` is a collective tree in which fields and element connectivities are not loaded) + +As explained in previous section, the standard full tree does not make sense in the context of a MPI parallel +application: all the trees encountered within the execution of maia are **distributed trees** or **partitioned trees**. +The next section describe the specification of these trees. + +Specification +^^^^^^^^^^^^^ + +Let us use the following tree as an example: + +.. image:: ./images/trees/tree_seq.png + +This tree is a **global tree**. It may appear like that on a HDF5/CGNS file, or if loaded entirely on one process as a Python/CGNS tree. + +.. _dist_tree: + +Distributed trees +""""""""""""""""" + +A :def:`dist tree` is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. + +If we distribute our tree over two processes, we would then have something like that: + +.. image:: ./images/trees/dist_tree.png + +Let us look at one of them and annotate nodes specific to the distributed tree: + +.. image:: ./images/trees/dist_tree_expl.png + +Arrays of non-constant size are distributed: fields, connectivities, :cgns:`PointLists`. +Others (:cgns:`PointRanges`, :cgns:`CGNSBase_t` and :cgns:`Zone_t` dimensions...) are of limited size and therefore replicated on all processes with virtually no memory penalty. + +On each process, for each entity kind, a **partial distribution** is stored, that gives information of which block of the arrays are stored locally. + +For example, for process 0, the distribution array of vertices of :cgns:`MyZone` is located at :cgns:`MyBase/MyZone/Distribution/Vertex` and is equal to :code:`[0, 9, 18]`. It means that only indices in the semi-open interval :code:`[0 9)` are stored by the **dist tree** on this process, and that the total size of the array is :code:`18`. +This partial distribution applies to arrays spaning all the vertices of the zone, e.g. :cgns:`CoordinateX`. + +More formally, a :def:`partial distribution` related to an entity kind :code:`E` is an array :code:`[start,end,total_size]` of 3 integers where :code:`[start:end)` is a closed/open interval giving, for all global arrays related to :code:`E`, the sub-array that is stored locally on the distributed tree, and :code:`total_size` is the global size of the arrays related to :code:`E`. + +The distributed entities are: + +.. glossary:: + Vertices and Cells + The **partial distribution** are stored in :cgns:`Distribution/Vertex` and :cgns:`Distribution/Cell` nodes at the level of the :cgns:`Zone_t` node. + + Used for example by :cgns:`GridCoordinates_t` and :cgns:`FlowSolution_t` nodes if they do not have a :cgns:`PointList` (i.e. if they span the entire vertices/cells of the zone) + + Quantities described by a :cgns:`PointList` or :cgns:`PointRange` + The **partial distribution** is stored in a :cgns:`Distribution/Index` node at the level of the :cgns:`PointList/PointRange` + + For example, :cgns:`ZoneSubRegion_t` and :cgns:`BCDataSet_t` nodes. + + If the quantity is described by a :cgns:`PointList`, then the :cgns:`PointList` itself is distributed the same way (in contrast, a :cgns:`PointRange` is fully replicated across processes because it is lightweight) + + Connectivities + The **partial distribution** is stored in a :cgns:`Distribution/Element` node at the level of the :cgns:`Element_t` node. Its values are related to the elements, not the vertices of the connectivity array. + + If the element type is heterogenous (NGon, NFace or MIXED) a :cgns:`Distribution/ElementConnectivity` is also present, and this partial distribution is related to the :cgns:`ElementConnectivity` array. + +.. note:: + A distributed tree object is not norm-compliant since most of its arrays are partial: on the previous example, + :cgns:`CoordinateX` array on rank 0 has a length of 9 when :cgns:`MyZone` declares 18 vertices. + However, the union of all the distributed tree objects represents a norm-compliant CGNS tree. + +.. _part_tree: + +Partitioned trees +""""""""""""""""" + +A :def:`part tree` is a partial CGNS tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. + +If we take the global tree from before and partition it, we may get the following tree: + +.. image:: ./images/trees/part_tree.png + +If we annotate the first one: + +.. image:: ./images/trees/part_tree_expl.png + +A **part tree** is just a regular, norm-compliant tree with additional information (in the form of :cgns:`GlobalNumbering` nodes) that keeps the link with the unpartitioned tree it comes from. Notice that the tree structure is **not** necessarily the same across all processes. + +The :cgns:`GlobalNumbering` nodes are located at the same positions that the :cgns:`Distribution` nodes were in the distributed tree. + +A :cgns:`GlobalNumbering` contains information to link an entity in the partition to its corresponding entity in the original tree. For example, the element section :cgns:`Hexa` has a global numbering array of value :code:`[3 4]`. It means: + +* Since it is an array of size 2, there is 2 elements in this section (which is confirmed by the :cgns:`ElementRange`) , +* The first element was the element of id :code:`3` in the original mesh, +* The second element was element :code:`4` in the original mesh. + +Naming conventions +"""""""""""""""""" + +When partitioning, some nodes are split, so there is convention to keep track of the fact they come from the same original node: + +* :cgns:`Zone_t` nodes : :cgns:`MyZone` is split in :cgns:`MyZone.PX.NY` where `X` is the rank of the process, and `Y` is the id of the zone on process `X`. +* Splitable nodes (notably :cgns:`GC_t`) : :cgns:`MyNode` is split in :cgns:`MyNode.N`. They appear in the following scenario: + + * We partition for 3 processes + * :cgns:`Zone0` is connected to :cgns:`Zone1` through :cgns:`GridConnectivity_0_to_1` + * :cgns:`Zone0` is not split (but goes to process 0 and becomes :cgns:`Zone0.P0.N0`). Zone1 is split into :cgns:`Zone1.P1.N0` and :cgns:`Zone1.P2.N0`. Then :cgns:`GridConnectivity_0_to_1` of :cgns:`Zone0` must be split into :cgns:`GridConnectivity_0_to_1.1` and :cgns:`GridConnectivity_0_to_1.2`. + +Note that partitioning may induce new :cgns:`GC_t` internal to the original zone being splitted. Their name is implementation-defined and those nodes do not have a :cgns:`GlobalNumbering` since they did not exist in the original mesh. + +.. _maia_tree: + +Maia trees +^^^^^^^^^^ + +A CGNS tree is said to be a :def:`Maia tree` if it has the following properties: + +* For each unstructured zone, the :cgns:`ElementRange` of all :cgns:`Elements_t` sections + + * are contiguous + * are ordered by ascending dimensions (i.e. edges come first, then faces, then cells) + * the first section starts at 1 + * there is at most one section by element type (e.g. not possible to have two :cgns:`QUAD_4` sections) + +Notice that this is property is required by **some** functions of Maia, not all of them! + +A **Maia tree** may be a **global tree**, a **distributed tree** or a **partitioned tree**. + +.. rubric:: Footnotes + +.. [#f1] Actually, such algorithm could be written for distributed data, but is less intuitive and require more knowledge of what + if happening on the other blocks. + +.. [#f2] Other distributions are possible : we could, for example, affect all the CoordinateX array on the first process (using the distribution array :code:`[0,12,12]`) and the CoordinateY array on the second, but we would have to manage a different distribution for each array. diff --git a/docs/1.1/_sources/license.rst.txt b/docs/1.1/_sources/license.rst.txt new file mode 100644 index 00000000..24330d26 --- /dev/null +++ b/docs/1.1/_sources/license.rst.txt @@ -0,0 +1,390 @@ +.. _license: + +License +======= + +Mozilla Public License Version 2.0 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Definitions +^^^^^^^^^^^^^^ + +**1.1. "Contributor"** + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +**1.2. "Contributor Version"** + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +**1.3. "Contribution"** + means Covered Software of a particular Contributor. + +**1.4. "Covered Software"** + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +**1.5. "Incompatible With Secondary Licenses"** + means + +* **(a)** that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or +* **(b)** that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +**1.6. "Executable Form"** + means any form of the work other than Source Code Form. + +**1.7. "Larger Work"** + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +**1.8. "License"** + means this document. + +**1.9. "Licensable"** + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +**1.10. "Modifications"** + means any of the following: + +* **(a)** any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or +* **(b)** any new file in Source Code Form that contains any Covered + Software. + +**1.11. "Patent Claims" of a Contributor** + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +**1.12. "Secondary License"** + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +**1.13. "Source Code Form"** + means the form of the work preferred for making modifications. + +**1.14. "You" (or "Your")** + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means **(a)** the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or **(b)** ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + + +2. License Grants and Conditions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +2.1. Grants +~~~~~~~~~~~ + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +* **(a)** under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and +* **(b)** under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date +~~~~~~~~~~~~~~~~~~~ + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +* **(a)** for any code that a Contributor has removed from Covered Software; + or +* **(b)** for infringements caused by: **(i)** Your and any other third party's + modifications of Covered Software, or **(ii)** the combination of its + Contributions with other software (except as part of its Contributor + Version); or +* **(c)** under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses +~~~~~~~~~~~~~~~~~~~~~~~~ + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation +~~~~~~~~~~~~~~~~~~~ + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use +~~~~~~~~~~~~~ + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions +~~~~~~~~~~~~~~~ + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + + +3. Responsibilities +^^^^^^^^^^^^^^^^^^^^^^^ + +3.1. Distribution of Source Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If You distribute Covered Software in Executable Form then: + +* **(a)** such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +* **(b)** You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices +~~~~~~~~~~~~ + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + + +4. Inability to Comply Due to Statute or Regulation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: **(a)** comply with +the terms of this License to the maximum extent possible; and **(b)** +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + + +5. Termination +^^^^^^^^^^^^^^ + +**5.1.** The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated **(a)** provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and **(b)** on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +**5.2.** If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + + +6. Disclaimer of Warranty +^^^^^^^^^^^^^^^^^^^^^^^^^ + + Covered Software is provided under this License on an "as is" + basis, without warranty of any kind, either expressed, implied, or + statutory, including, without limitation, warranties that the + Covered Software is free of defects, merchantable, fit for a + particular purpose or non-infringing. The entire risk as to the + quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You + (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an + essential part of this License. No use of any Covered Software is + authorized under this License except under this disclaimer. + +7. Limitation of Liability +^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Under no circumstances and under no legal theory, whether tort + (including negligence), contract, or otherwise, shall any + Contributor, or anyone who distributes Covered Software as + permitted above, be liable to You for any direct, indirect, + special, incidental, or consequential damages of any character + including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any + and all other commercial damages or losses, even if such party + shall have been informed of the possibility of such damages. This + limitation of liability shall not apply to liability for death or + personal injury resulting from such party's negligence to the + extent applicable law prohibits such limitation. Some + jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and + limitation may not apply to You. + + +8. Litigation +^^^^^^^^^^^^^ + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + + +9. Miscellaneous +^^^^^^^^^^^^^^^^ + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + + +10. Versions of the License +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +10.1. New Versions +~~~~~~~~~~~~~~~~~~ + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions +~~~~~~~~~~~~~~~~~~~~~~~ + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary License" Notice +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + + diff --git a/docs/1.1/_sources/quick_start.rst.txt b/docs/1.1/_sources/quick_start.rst.txt new file mode 100644 index 00000000..af0fba63 --- /dev/null +++ b/docs/1.1/_sources/quick_start.rst.txt @@ -0,0 +1,167 @@ +.. _quick_start: + +.. currentmodule:: maia + +Quick start +=========== + +Environnements +-------------- + +Maia is now distributed in elsA releases (since v5.2.01) ! + +If you want to try the latest features, we provide ready-to-go environments including Maia and its dependencies on the following clusters: + +**Spiro-EL8** + +This is the recommended environment for standalone usage of Maia. It works with intel mpi library (2021) +and python version 3.9. + +.. code-block:: sh + + source /scratchm/sonics/dist/source.sh --env maia + module load maia/dev-default + +If you want to use Maia within the standard Spiro environment, the next installation is compatible with +the socle socle-cfd/5.0-intel2120-impi: + +.. code-block:: sh + + module use --append /scratchm/sonics/usr/modules/ + module load maia/dev-dsi-cfd5 + +Note that this is the environment used by elsA for its production spiro-el8_mpi. + +**Sator** + +Similarly, Maia installation are available in both the self maintained and standard socle +on Sator cluster. Sator's version is compiled with support of large integers. + +.. code-block:: sh + + # Versions based on self compiled tools + source /tmp_user/sator/sonics/dist/source.sh --env maia + module load maia/dev-default + + # Versions based on socle-cfd compilers and tools + module use --append /tmp_user/sator/sonics/usr/modules/ + module load maia/dev-dsi-cfd5 + + +If you prefer to build your own version of Maia, see :ref:`installation` section. + +Supported meshes +---------------- + +Maia supports CGNS meshes from version 4.2, meaning that polyhedral connectivities (NGON_n, NFACE_n +and MIXED nodes) must have the ``ElementStartOffset`` node. + +Former meshes can be converted with the (sequential) maia_poly_old_to_new script included +in the ``$PATH`` once the environment is loaded: + +.. code-block:: sh + + $> maia_poly_old_to_new mesh_file.hdf + +The opposite maia_poly_new_to_old script can be used to put back meshes +in old conventions, insuring compatibility with legacy tools. + +.. warning:: CGNS databases should respect the `SIDS `_. + The most commonly observed non-compliant practices are: + + - Empty ``DataArray_t`` (of size 0) under ``FlowSolution_t`` containers. + - 2D shaped (N1,N2) ``DataArray_t`` under ``BCData_t`` containers. + These arrays should be flat (N1xN2,). + - Implicit ``BCDataSet_t`` location for structured meshes: if ``GridLocation_t`` + and ``PointRange_t`` of a given ``BCDataSet_t`` differs from the + parent ``BC_t`` node, theses nodes should be explicitly defined at ``BCDataSet_t`` + level. + + Several non-compliant practices can be detected with the ``cgnscheck`` utility. Do not hesitate + to check your file if Maia is unable to read it. + +Note also that ADF files are not supported; CGNS files should use the HDF binary format. ADF files can +be converted to HDF thanks to ``cgnsconvert``. + +Highlights +---------- + +.. tip:: Download sample files of this section: + :download:`S_twoblocks.cgns <../share/_generated/S_twoblocks.cgns>`, + :download:`U_ATB_45.cgns <../share/_generated/U_ATB_45.cgns>` + +.. rubric:: Daily user-friendly pre & post processing + +Maia provides simple Python APIs to easily setup pre or post processing operations: +for example, converting a structured tree into an unstructured (NGon) tree +is as simple as + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #basic_algo@start + :end-before: #basic_algo@end + :dedent: 2 + +.. image:: ./images/qs_basic.png + :width: 75% + :align: center + +In addition of being parallel, the algorithms are as much as possible topologic, meaning that +they do not rely on a geometric tolerance. This also allow us to preserve the boundary groups +included in the input mesh (colored on the above picture). + +.. rubric:: Building efficient workflows + +By chaining this elementary blocks, you can build a **fully parallel** advanced workflow +running as a **single job** and **minimizing file usage**. + +In the following example, we load an angular section of the +`ATB case `_, +duplicate it to a 180° case, split it, and perform some slices and extractions. + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #workflow@start + :end-before: #workflow@end + :dedent: 2 + +.. image:: ./images/qs_workflow.png + +The above illustration represents the input mesh (gray volumic block) and the +extracted surfacic tree (plane slice and extracted wall BC). Curved lines are +the outline of the volumic mesh after duplication. + +.. rubric:: Compliant with the pyCGNS world + +Finally, since Maia uses the standard `CGNS/Python mapping +`_, +you can set up applications involving multiple python packages: +here, we create and split a mesh with maia, but we then call Cassiopee functions +to compute the gradient of a field on each partition. + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #pycgns@start + :end-before: #pycgns@end + :dedent: 2 + +.. image:: ./images/qs_pycgns.png + +Be aware that other tools often expect to receive geometrically +consistent data, which is why we send the partitioned tree to +Cassiopee functions. The outline of this partitions (using 4 processes) +are materialized by the white lines on the above figure. + + +Resources and Troubleshouting +----------------------------- + +The :ref:`user manual ` describes most of the high level APIs provided by Maia. +If you want to be comfortable with the underlying concepts of distributed and +partitioned trees, have a look at the :ref:`introduction ` section. + +The user manual is illustrated with basic examples. Additional +test cases can be found in +`the sources `_. + +Issues can be reported on +`the gitlab board `_ +and help can also be asked on the dedicated +`Element room `_. diff --git a/docs/1.1/_sources/related_projects.rst.txt b/docs/1.1/_sources/related_projects.rst.txt new file mode 100644 index 00000000..570528b9 --- /dev/null +++ b/docs/1.1/_sources/related_projects.rst.txt @@ -0,0 +1,28 @@ +.. _related: + +################ +Related projects +################ + +CGNS +---- + +The CFD General Notation System (CGNS) provides a general, portable, and extensible standard +for the storage and retrieval of computational fluid dynamics (CFD) analysis data. + +https://cgns.github.io/ + +ParaDiGM +-------- + +The ParaDiGM library provide the developers a progressive framework, which consists of a set of +low-, mid- and high-level services helpfull to write scientific +computing software that rely on a mesh. + +Cassiopée +--------- + +A set of python modules for pre- and post-processing of CFD computations + +http://elsa.onera.fr/Cassiopee/ + diff --git a/docs/1.1/_sources/releases/release_notes.rst.txt b/docs/1.1/_sources/releases/release_notes.rst.txt new file mode 100644 index 00000000..b53cbd23 --- /dev/null +++ b/docs/1.1/_sources/releases/release_notes.rst.txt @@ -0,0 +1,46 @@ +.. _release_notes: + +Release notes +============= + +.. _whatsnew: + +.. currentmodule:: maia + +This page contains information about what has changed in each new version of **Maia**. + +v1.1 (May 2023) +--------------- + +New Features +^^^^^^^^^^^^ + +- Algo module: generate (periodic) 1to1 GridConnectivity between selected BC or GC +- Factory module: generate 2D spherical meshes and points clouds + +Feature improvements +^^^^^^^^^^^^^^^^^^^^ +- generate_dist_block: enable generation of structured meshes +- partitioning: enable split of 2D (NGON/Elts) and 1D (Elts) meshes +- partitioning: copy AdditionalFamilyName and ReferenceState from BCs to the partitions +- compute_face_center : manage structured meshes +- merge_zones: allow wildcards in zone_paths +- isosurface: recover volumic GCs on surfacic tree (as BCs) +- transfer (part->dist): manage BC/BCDataSet created on partitions for structured meshes + +Fixes +^^^^^ +- convert_elements_to_ngon: prevent a memory error & better management of 2D meshes +- isosurface: improve robustness of edge reconstruction +- partitioning: fix split of structured GCs and BCDataSet +- merge_zone: fix a bug occurring when FamilyName appears under some BC_t nodes + +Advanced users / devs +^^^^^^^^^^^^^^^^^^^^^ +- use new pytest_parallel module +- transfer (part->dist): add user callback to reduce shared entities + + +v1.0 (March 2023) +----------------- +First release of Maia ! diff --git a/docs/1.1/_sources/user_manual/algo.rst.txt b/docs/1.1/_sources/user_manual/algo.rst.txt new file mode 100644 index 00000000..9ac6c958 --- /dev/null +++ b/docs/1.1/_sources/user_manual/algo.rst.txt @@ -0,0 +1,109 @@ +Algo module +=========== + +The ``maia.algo`` module provides various algorithms to be applied to one of the +two kind of trees defined by Maia: + +- ``maia.algo.dist`` module contains some operations applying on distributed trees +- ``maia.algo.part`` module contains some operations applying on partitioned trees + +In addition, some algorithms can be applied indistinctly to distributed or partitioned trees. +These algorithms are accessible through the ``maia.algo`` module. + +The ``maia.algo.seq`` module contains a few sequential utility algorithms. + +.. _user_man_dist_algo: + +Distributed algorithms +---------------------- + +The following algorithms applies on maia distributed trees. + + +Connectivities conversions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.convert_s_to_u +.. autofunction:: maia.algo.dist.convert_elements_to_ngon +.. autofunction:: maia.algo.dist.ngons_to_elements +.. autofunction:: maia.algo.dist.convert_elements_to_mixed +.. autofunction:: maia.algo.dist.convert_mixed_to_elements +.. autofunction:: maia.algo.dist.rearrange_element_sections +.. autofunction:: maia.algo.dist.generate_jns_vertex_list + + +Geometry transformations +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.duplicate_from_rotation_jns_to_360 +.. autofunction:: maia.algo.dist.merge_zones +.. autofunction:: maia.algo.dist.merge_zones_from_family +.. autofunction:: maia.algo.dist.merge_connected_zones +.. autofunction:: maia.algo.dist.conformize_jn_pair + +Interface tools +^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.connect_1to1_families + +Data management +^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.redistribute_tree + +.. + from .extract_surf_dmesh import extract_surf_tree_from_bc + +.. _user_man_part_algo: + +Partitioned algorithms +---------------------- + +The following algorithms applies on maia partitioned trees. + +Geometric calculations +^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.compute_cell_center +.. autofunction:: maia.algo.part.compute_face_center +.. autofunction:: maia.algo.part.compute_edge_center +.. autofunction:: maia.algo.part.compute_wall_distance +.. autofunction:: maia.algo.part.localize_points +.. autofunction:: maia.algo.part.find_closest_points + +Mesh extractions +^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.iso_surface +.. autofunction:: maia.algo.part.plane_slice +.. autofunction:: maia.algo.part.spherical_slice +.. autofunction:: maia.algo.part.extract_part_from_zsr +.. autofunction:: maia.algo.part.extract_part_from_bc_name + +Interpolations +^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.interpolate_from_part_trees +.. autofunction:: maia.algo.part.centers_to_nodes + +.. _user_man_gen_algo: + + +Generic algorithms +------------------ + +The following algorithms applies on maia distributed or partitioned trees + +.. autofunction:: maia.algo.transform_affine +.. autofunction:: maia.algo.pe_to_nface +.. autofunction:: maia.algo.nface_to_pe + + +Sequential algorithms +--------------------- + +The following algorithms applies on regular pytrees. + +.. autofunction:: maia.algo.seq.poly_new_to_old +.. autofunction:: maia.algo.seq.poly_old_to_new +.. autofunction:: maia.algo.seq.enforce_ngon_pe_local diff --git a/docs/1.1/_sources/user_manual/config.rst.txt b/docs/1.1/_sources/user_manual/config.rst.txt new file mode 100644 index 00000000..bb948961 --- /dev/null +++ b/docs/1.1/_sources/user_manual/config.rst.txt @@ -0,0 +1,53 @@ +Configuration +============= + + +Logging +------- + +Maia provides informations to the user through the loggers summarized +in the following table: + ++--------------+-----------------------+---------------------------+ +| Logger | Purpose | Default printer | ++==============+=======================+===========================+ +| maia | Light general info | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-warnings| Warnings | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-errors | Errors | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-stats | More detailed timings | No output | +| | and memory usage | | ++--------------+-----------------------+---------------------------+ + +The easiest way to change this default configuration is to +set the environment variable ``LOGGING_CONF_FILE`` to provide a file +(e.g. logging.conf) looking like this: + +.. code-block:: Text + + maia : mpi_stdout_printer # All ranks output to sdtout + maia-warnings : # No output + maia-errors : mpi_rank_0_stderr_printer # Rank 0 output to stderr + maia-stats : file_printer("stats.log") # All ranks output in the file + +See :ref:`logging` for a full description of available +printers or for instructions on how to create your own printers. +This page also explain how to dynamically change printers +directly in your Python application. + +Exception handling +------------------ + +Maia automatically override the `sys.excepthook +`_ +function to call ``MPI_Abort`` when an uncatched exception occurs. +This allow us to terminate the MPI execution and to avoid some deadlocks +if an exception is raised by a single process. +Note that this call force the global ``COMM_WORLD`` communicator to abort which +can have undesired effects if sub communicators are used. + +This behaviour can be disabled with a call to +``maia.excepthook.disable_mpi_excepthook()``. + diff --git a/docs/1.1/_sources/user_manual/factory.rst.txt b/docs/1.1/_sources/user_manual/factory.rst.txt new file mode 100644 index 00000000..ce362d52 --- /dev/null +++ b/docs/1.1/_sources/user_manual/factory.rst.txt @@ -0,0 +1,117 @@ +Factory module +============== + +The ``maia.factory`` module can be used to generate parallel CGNS trees. Those can +be obtained from an other kind of tree, or can be generated from user parameters. + +Generation +---------- + +.. autofunction:: maia.factory.generate_dist_points +.. autofunction:: maia.factory.generate_dist_block +.. autofunction:: maia.factory.generate_dist_sphere +.. autofunction:: maia.factory.distribute_tree + +Partitioning +------------ + +.. autofunction:: maia.factory.partition_dist_tree + +Partitioning options +^^^^^^^^^^^^^^^^^^^^ +Partitioning can be customized with the following keywords arguments: + +.. py:attribute:: graph_part_tool + + Method used to split unstructured blocks. Irrelevent for structured blocks. + + :Admissible values: + - ``parmetis``, ``ptscotch`` : graph partitioning methods, + - ``hilbert`` : geometric method (only for NGon connectivities), + - ``gnum`` : cells are dispatched according to their absolute numbering. + + :Default value: ``parmetis``, if installed; else ``ptscotch``, if installed; ``hilbert`` otherwise. + +.. py:attribute:: zone_to_parts + + Control the number, size and repartition of partitions. See :ref:`user_man_part_repartition`. + + :Default value: Computed such that partitioning is balanced using + :func:`maia.factory.partitioning.compute_balanced_weights`. + +.. py:attribute:: part_interface_loc + + :cgns:`GridLocation` for the created partitions interfaces. Pre-existing interface keep their original location. + + :Admissible values: ``FaceCenter``, ``Vertex`` + :Default value: ``FaceCenter`` for unstructured zones with NGon connectivities; ``Vertex`` otherwise. + +.. py:attribute:: reordering + + Dictionnary of reordering options, which are used to renumber the entities in each partitioned zone. See + corresponding documentation. + +.. py:attribute:: preserve_orientation + + If True, the created interface faces are not reversed and keep their original orientation. Consequently, + NGonElements can have a zero left parent and a non zero right parent. + Only relevant for U/NGon partitions. + + :Default value: ``False`` + +.. py:attribute:: dump_pdm_output + + If True, dump the raw arrays created by paradigm in a :cgns:`CGNSNode` at (partitioned) zone level. For debug only. + + :Default value: ``False`` + +.. _user_man_part_repartition: + +Repartition +^^^^^^^^^^^ + +The number, size, and repartition (over the processes) of the created partitions is +controlled through the ``zone_to_parts`` keyword argument: each process must provide a +dictionary associating the path of every distributed zone to a list of floats. +For a given distributed zone, current process will receive as many partitions +as the length of the list (missing path or empty list means no partitions); the size of these partitions +corresponds to the values in the list (expressed in fraction of the input zone). +For a given distributed zone, the sum of all the fractions across all the processes must +be 1. + +This dictionary can be created by hand; for convenience, Maia provides three functions in the +:mod:`maia.factory.partitioning` module to create this dictionary. + +.. autofunction:: maia.factory.partitioning.compute_regular_weights +.. autofunction:: maia.factory.partitioning.compute_balanced_weights +.. autofunction:: maia.factory.partitioning.compute_nosplit_weights + +Reordering options +^^^^^^^^^^^^^^^^^^ + +For unstructured zones, the reordering options are transmitted to ParaDiGM in order to +control the renumbering of mesh entities in the partitions. + ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| Kwarg | Admissible values | Effect | Default | ++====================+===================================+=====================================+============================+ +| cell_renum_method | "NONE", "RANDOM", "HILBERT", | Renumbering method for the cells | NONE | +| | "CUTHILL", "CACHEBLOCKING", | | | +| | "CACHEBLOCKING2", "HPC" | | | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| face_renum_method | "NONE", "RANDOM", "LEXICOGRAPHIC" | Renumbering method for the faces | NONE | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| vtx_renum_method | "NONE", "SORT_INT_EXT" | Renumbering method for the vertices | NONE | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| n_cell_per_cache | Integer >= 0 | Specific to cacheblocking | 0 | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| n_face_per_pack | Integer >= 0 | Specific to cacheblocking | 0 | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| graph_part_tool | "parmetis", "ptscotch", | Graph partitioning library to | Same as partitioning tool | +| | "hyperplane" | use for renumbering | | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ + +Recovering from partitions +-------------------------- + +.. autofunction:: maia.factory.recover_dist_tree diff --git a/docs/1.1/_sources/user_manual/io.rst.txt b/docs/1.1/_sources/user_manual/io.rst.txt new file mode 100644 index 00000000..a0ad5f69 --- /dev/null +++ b/docs/1.1/_sources/user_manual/io.rst.txt @@ -0,0 +1,79 @@ +File management +=============== + +Maia supports HDF5/CGNS file reading and writing, +see `related documention `_. + +The IO functions are provided by the ``maia.io`` module. All the high level functions +accepts a ``legacy`` parameter used to control the low level CGNS-to-hdf driver: + +- if ``legacy==False`` (default), hdf calls are performed by the python module + `h5py `_. +- if ``legacy==True``, hdf calls are performed by + `Cassiopee.Converter `_ module. + +The requested driver should be installed on your computer as well as the +hdf5 library compiled with parallel support. + +.. _user_man_dist_io: + +Distributed IO +-------------- + +Distributed IO is the privileged way to deal with CGNS files within your maia workflows. +Files are loaded as distributed trees, and, inversely, distributed trees can be written +into a single CGNS file. + +High level IO operations can be performed with the two following functions, which read +or write all data they found : + +.. autofunction:: maia.io.file_to_dist_tree +.. autofunction:: maia.io.dist_tree_to_file + + +The example below shows how to uses these high level functions: + +.. literalinclude:: snippets/test_io.py + :start-after: #file_to_dist_tree_full@start + :end-before: #file_to_dist_tree_full@end + :dedent: 2 + +Finer control of what is written or loaded can be achieved with the following steps: + +- For a **write** operation, the easiest way to write only some nodes in + the file is to remove the unwanted nodes from the distributed tree. +- For a **read** operation, the load has to be divided into the following steps: + + - Loading a size_tree: this tree has only the shape of the distributed data and + not the data itself. + - Removing unwanted nodes in the size tree; + - Fill the filtered tree from the file. + +The example below illustrate how to filter the written or loaded nodes: + +.. literalinclude:: snippets/test_io.py + :start-after: #file_to_dist_tree_filter@start + :end-before: #file_to_dist_tree_filter@end + :dedent: 2 + +Writing partitioned trees +-------------------------- + +In some cases, it may be useful to write a partitioned tree (keeping the +partitioned zones separated). This can be achieved using the following function: + +.. autofunction:: maia.io.part_tree_to_file + +.. _user_man_raw_io: + +Raw IO +------ + +For debug purpose, trees can also be read or written independently in a +sequential manner. Be aware that information added by maia such as Distribution +or GlobalNumbering nodes will not be removed. + +.. autofunction:: maia.io.read_tree +.. autofunction:: maia.io.write_tree +.. autofunction:: maia.io.write_trees + diff --git a/docs/1.1/_sources/user_manual/transfer.rst.txt b/docs/1.1/_sources/user_manual/transfer.rst.txt new file mode 100644 index 00000000..4b5d7895 --- /dev/null +++ b/docs/1.1/_sources/user_manual/transfer.rst.txt @@ -0,0 +1,92 @@ +Transfer module +=============== + +The ``maia.transfer`` contains functions that exchange data between the +partitioned and distributed meshes. + +Fields transfer +--------------- + +High level APIs allow to exchange data at CGNS :cgns:`Tree` or :cgns:`Zone` level. +All the provided functions take similar parameters: a distributed tree (resp. zone), +the corresponding partitioned tree (resp. list of zones), the MPI communicator and +optionally a filtering parameter. + +The following kind of data are supported: +:cgns:`FlowSolution_t`, :cgns:`DiscreteData_t`, :cgns:`ZoneSubRegion_t` and :cgns:`BCDataSet_t`. + +When transferring from distributed meshes to partitioned meshes, fields are supposed +to be known on the source mesh across all the ranks (according to :ref:`disttree definition`). +Geometric patches (such as ZoneSubRegion or BCDataSet) must exists on the relevant partitions, meaning +that only fields are transfered. + +When transferring from partitioned meshes to distributed meshes, geometric patches may or may not exist on the +distributed zone: they will be created if needed. This allows to transfer fields that have been created +on the partitioned meshes. +It is however assumed that global data (*e.g.* FlowSolution) are defined on every partition. + +Tree level +^^^^^^^^^^ + +All the functions of this section operate inplace and require the following parameters: + +- **dist_tree** (*CGNSTree*) -- Distributed CGNS Tree +- **part_tree** (*CGNSTree*) -- Corresponding partitioned CGNS Tree +- **comm** (*MPIComm*) -- MPI communicator + +.. autofunction:: maia.transfer.dist_tree_to_part_tree_all +.. autofunction:: maia.transfer.part_tree_to_dist_tree_all + +In addition, the next two methods expect the parameter **labels** (*list of str*) +which allow to pick one or more kind of data to transfer from the supported labels. + +.. autofunction:: maia.transfer.dist_tree_to_part_tree_only_labels +.. autofunction:: maia.transfer.part_tree_to_dist_tree_only_labels + +Zone level +^^^^^^^^^^ + +All the functions of this section operate inplace and require the following parameters: + +- **dist_zone** (*CGNSTree*) -- Distributed CGNS Zone +- **part_zones** (*list of CGNSTree*) -- Corresponding partitioned CGNS Zones +- **comm** (*MPIComm*) -- MPI communicator + +In addition, filtering is possible with the use of the +**include_dict** or **exclude_dict** dictionaries. These dictionaries map +each supported label to a list of cgns paths to include (or exclude). Paths +starts from the ``Zone_t`` node and ends at the targeted ``DataArray_t`` node. +Wildcard ``*`` are allowed in paths : for example, considering the following tree +structure, + +.. code:: + + Zone (Zone_t) + ├── FirstSolution (FlowSolution_t) + │   ├── Pressure (DataArray_t) + │   ├── MomentumX (DataArray_t) + │   └── MomentumY (DataArray_t) + ├── SecondSolution (FlowSolution_t) + │   ├── Pressure (DataArray_t) + │   ├── MomentumX (DataArray_t) + │   └── MomentumY (DataArray_t) + └── SpecialSolution (FlowSolution_t) +    ├── Density (DataArray_t) +    └── MomentumZ (DataArray_t) + +| ``"FirstSolution/Momentum*"`` maps to ``["FirstSolution/MomentumX", "FirstSolution/MomentumY"]``, +| ``"*/Pressure`` maps to ``["FirstSolution/Pressure", "SecondSolution/Pressure"]``, and +| ``"S*/M*"`` maps to ``["SecondSolution/MomentumX", "SecondSolution/MomentumY", "SpecialSolution/MomentumZ"]``. + +For convenience, we also provide the magic path ``['*']`` meaning "everything related to this +label". + +Lastly, we use the following rules to manage missing label keys in dictionaries: + + - For _only functions, we do not transfer any field related to the missing labels; + - For _all functions, we do transfer all the fields related to the missing labels. + +.. autofunction:: maia.transfer.dist_zone_to_part_zones_only +.. autofunction:: maia.transfer.part_zones_to_dist_zone_only +.. autofunction:: maia.transfer.dist_zone_to_part_zones_all +.. autofunction:: maia.transfer.part_zones_to_dist_zone_all diff --git a/docs/1.1/_sources/user_manual/user_manual.rst.txt b/docs/1.1/_sources/user_manual/user_manual.rst.txt new file mode 100644 index 00000000..aedb0917 --- /dev/null +++ b/docs/1.1/_sources/user_manual/user_manual.rst.txt @@ -0,0 +1,49 @@ +.. _user_manual: + +########### +User Manual +########### + +Maia methods are accessible through three main modules : + +- **Factory** allows to generate Maia trees, generally from another kind of tree + (e.g. the partitioning operation). Factory functions return a new tree + whose nature generally differs from the input tree. + +- **Algo** is the main Maia module and provides parallel algorithms to be applied on Maia trees. + Some algorithms are only available for :ref:`distributed trees ` + and some are only available for :ref:`partitioned trees `. + A few algorithms are implemented for both kind + of trees and are thus directly accessible through the :ref:`algo ` module. + + Algo functions either modify their input tree inplace, or return some data, but they do not change the nature + of the tree. + +- **Transfer** is a small module allowing to transfer data between Maia trees. A transfer function operates + on two existing trees and enriches the destination tree with data fields of the source tree. + +Using Maia trees in your application often consists in chaining functions from these different modules. + +.. image:: ./images/workflow.svg + +A typical workflow could be: + +1. Load a structured tree from a file, which produces a **dist tree**. +2. Apply some distributed algorithms to this tree: for example a structured to unstructured conversion (``algo.dist`` module). +3. Generate a corresponding partitionned tree (``factory`` module). +4. Apply some partitioned algorithms to the **part tree**, such as wall distance computation (``algo.part`` module), + and even call you own tools (e.g. a CFD solver) +5. Transfer the resulting fields to the **dist tree** (``transfer`` module). +6. Save the updated dist tree to disk. + +This user manuel describes the main functions available in each module. + +.. toctree:: + :maxdepth: 1 + :hidden: + + config + io + factory + algo + transfer diff --git a/docs/1.1/_static/basic.css b/docs/1.1/_static/basic.css new file mode 100644 index 00000000..bf18350b --- /dev/null +++ b/docs/1.1/_static/basic.css @@ -0,0 +1,906 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/1.1/_static/css/badge_only.css b/docs/1.1/_static/css/badge_only.css new file mode 100644 index 00000000..e380325b --- /dev/null +++ b/docs/1.1/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/1.1/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/1.1/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/1.1/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/1.1/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/1.1/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/1.1/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/1.1/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/1.1/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/1.1/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/1.1/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/1.1/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/1.1/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/1.1/_static/css/fonts/fontawesome-webfont.eot b/docs/1.1/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/docs/1.1/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/1.1/_static/css/fonts/fontawesome-webfont.svg b/docs/1.1/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/docs/1.1/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/1.1/_static/css/fonts/fontawesome-webfont.ttf b/docs/1.1/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/docs/1.1/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/1.1/_static/css/fonts/fontawesome-webfont.woff b/docs/1.1/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/docs/1.1/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/1.1/_static/css/fonts/fontawesome-webfont.woff2 b/docs/1.1/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/docs/1.1/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/1.1/_static/css/fonts/lato-bold-italic.woff b/docs/1.1/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/1.1/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/1.1/_static/css/fonts/lato-bold-italic.woff2 b/docs/1.1/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/1.1/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/1.1/_static/css/fonts/lato-bold.woff b/docs/1.1/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/1.1/_static/css/fonts/lato-bold.woff differ diff --git a/docs/1.1/_static/css/fonts/lato-bold.woff2 b/docs/1.1/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/1.1/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/1.1/_static/css/fonts/lato-normal-italic.woff b/docs/1.1/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/1.1/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/1.1/_static/css/fonts/lato-normal-italic.woff2 b/docs/1.1/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/1.1/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/1.1/_static/css/fonts/lato-normal.woff b/docs/1.1/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/1.1/_static/css/fonts/lato-normal.woff differ diff --git a/docs/1.1/_static/css/fonts/lato-normal.woff2 b/docs/1.1/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/1.1/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/1.1/_static/css/read_the_docs_custom.css b/docs/1.1/_static/css/read_the_docs_custom.css new file mode 100644 index 00000000..f164cfdf --- /dev/null +++ b/docs/1.1/_static/css/read_the_docs_custom.css @@ -0,0 +1,30 @@ +@import 'theme.css'; /* for the Read the Docs theme */ + +.rst-content div[class^="highlight"] pre { + font-size: 95%; +} + +/* +div.indexpage p { + font-size: 5.0em; +} +*/ + +/* override table no-wrap */ +.wy-table-responsive table td, .wy-table-responsive table th { + white-space: normal; +} + +.def { + color: #e41a1c; + font-weight: bold; +} + +.mono { + font-family: 'Courier New', monospace; +} + +.cgns { + color: #08306b; + font-family: 'Courier New', monospace; +} diff --git a/docs/1.1/_static/css/theme.css b/docs/1.1/_static/css/theme.css new file mode 100644 index 00000000..8cd4f101 --- /dev/null +++ b/docs/1.1/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li span.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li span.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li span.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li span.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li span.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p.caption .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.btn .wy-menu-vertical li span.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p.caption .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.nav .wy-menu-vertical li span.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p.caption .btn .headerlink,.rst-content p.caption .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li span.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol li,.rst-content ol.arabic li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content ol.arabic li p:last-child,.rst-content ol.arabic li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover span.toctree-expand,.wy-menu-vertical li.on a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand{display:block;font-size:.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp{user-select:none;pointer-events:none}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink{visibility:hidden;font-size:14px}.rst-content .code-block-caption .headerlink:after,.rst-content .toctree-wrapper>p.caption .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content p.caption .headerlink:after,.rst-content table>caption .headerlink:after{content:"\f0c1";font-family:FontAwesome}.rst-content .code-block-caption:hover .headerlink:after,.rst-content .toctree-wrapper>p.caption:hover .headerlink:after,.rst-content dl dt:hover .headerlink:after,.rst-content h1:hover .headerlink:after,.rst-content h2:hover .headerlink:after,.rst-content h3:hover .headerlink:after,.rst-content h4:hover .headerlink:after,.rst-content h5:hover .headerlink:after,.rst-content h6:hover .headerlink:after,.rst-content p.caption:hover .headerlink:after,.rst-content table>caption:hover .headerlink:after{visibility:visible}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl dt span.classifier:before{content:" : "}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code,html.writer-html4 .rst-content dl:not(.docutils) tt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/1.1/_static/doctools.js b/docs/1.1/_static/doctools.js new file mode 100644 index 00000000..e509e483 --- /dev/null +++ b/docs/1.1/_static/doctools.js @@ -0,0 +1,326 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + var url = new URL(window.location); + url.searchParams.delete('highlight'); + window.history.replaceState({}, '', url); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + break; + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + break; + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/1.1/_static/documentation_options.js b/docs/1.1/_static/documentation_options.js new file mode 100644 index 00000000..2fa8c97f --- /dev/null +++ b/docs/1.1/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/1.1/_static/file.png b/docs/1.1/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/docs/1.1/_static/file.png differ diff --git a/docs/1.1/_static/fonts/Lato/lato-bold.eot b/docs/1.1/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 00000000..3361183a Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/1.1/_static/fonts/Lato/lato-bold.ttf b/docs/1.1/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 00000000..29f691d5 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/1.1/_static/fonts/Lato/lato-bold.woff b/docs/1.1/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/1.1/_static/fonts/Lato/lato-bold.woff2 b/docs/1.1/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/1.1/_static/fonts/Lato/lato-bolditalic.eot b/docs/1.1/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 00000000..3d415493 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/1.1/_static/fonts/Lato/lato-bolditalic.ttf b/docs/1.1/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 00000000..f402040b Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/1.1/_static/fonts/Lato/lato-bolditalic.woff b/docs/1.1/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/1.1/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/1.1/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/1.1/_static/fonts/Lato/lato-italic.eot b/docs/1.1/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 00000000..3f826421 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/1.1/_static/fonts/Lato/lato-italic.ttf b/docs/1.1/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 00000000..b4bfc9b2 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/1.1/_static/fonts/Lato/lato-italic.woff b/docs/1.1/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/1.1/_static/fonts/Lato/lato-italic.woff2 b/docs/1.1/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/1.1/_static/fonts/Lato/lato-regular.eot b/docs/1.1/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 00000000..11e3f2a5 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/1.1/_static/fonts/Lato/lato-regular.ttf b/docs/1.1/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 00000000..74decd9e Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/1.1/_static/fonts/Lato/lato-regular.woff b/docs/1.1/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/1.1/_static/fonts/Lato/lato-regular.woff2 b/docs/1.1/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/1.1/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 00000000..79dc8efe Binary files /dev/null and b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 00000000..df5d1df2 Binary files /dev/null and b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 00000000..2f7ca78a Binary files /dev/null and b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 00000000..eb52a790 Binary files /dev/null and b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/1.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/1.1/_static/graphviz.css b/docs/1.1/_static/graphviz.css new file mode 100644 index 00000000..19e7afd3 --- /dev/null +++ b/docs/1.1/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/docs/1.1/_static/jquery-3.5.1.js b/docs/1.1/_static/jquery-3.5.1.js new file mode 100644 index 00000000..50937333 --- /dev/null +++ b/docs/1.1/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Algorithms description

+

This section provides a detailed description of some algorithms.

+ +
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/developer_manual/algo_description/elements_to_ngons.html b/docs/1.1/developer_manual/algo_description/elements_to_ngons.html new file mode 100644 index 00000000..b59b5349 --- /dev/null +++ b/docs/1.1/developer_manual/algo_description/elements_to_ngons.html @@ -0,0 +1,506 @@ + + + + + + + + + + elements_to_ngons — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

elements_to_ngons

+
+

Description

+
maia.algo.dist.elements_to_ngons(dist_tree_elts, comm)
+
+
+

Take a distributed CGNSTree_t or CGNSBase_t, and transform it into a distributed NGon/NFace mesh. The tree is modified in-place.

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD, stable_sort=True)
+
+
+
+
+

Arguments

+
+
dist_tree_elts
+
a distributed tree with
    +
  • unstructured zones that contain regular element sections (i.e. not NGON, not NFACE and not MIXED)

  • +
  • the Maia tree property

  • +
+
+
+
+
comm

a communicator over which elements_to_ngons is called

+
+
+
+
+

Output

+

The tree is modified in-place. The regular element sections are replaced by:

+
    +
  • a NGon section with:

    +
      +
    • An ElementStartOffset/ElementConnectivity node describing:

      +
        +
      • first the external faces in exactly the same order as they were (in particular, gathered by face type)

      • +
      • then the internal faces, also gathered by face type

      • +
      +
    • +
    • a ParentElements node and a ParentElementsPosition node

    • +
    +
  • +
  • a NFace section with cells ordered as they were, with ids only shifted by the number of interior faces

  • +
+
+
+

Parallelism

+

elements_to_ngons is a collective function.

+
+
+

Complexity

+

With \(N\) the number of zones, \(n_i\) the number of elements of zone \(i\), \(n_{f,i}\) its number of interior faces, and \(K\) the number of processes

+
+
Sequential time

\(\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i \cdot log(n_i) \right)\)

+

The cost is dominated by the sorting of faces (interior and exterior) used to spot identical ones.

+
+
Parallel time

\(\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i/K \cdot log(n_i) \right)\)

+

The parallel distributed sort algorithm consists of three steps:

+
+
    +
  1. A partitioning step that locally gather faces into \(K\) buckets that are of equal global size. This uses a \(K\)-generalized variant of quickselect that is of complexity \(\displaystyle \mathcal{O} \left( n_i/K \cdot log(K) \right)\)

  2. +
  3. An all_to_all exchange step that gather the \(K\) buckets on the \(K\) processes. This step is not accounted for here (see below)

  4. +
  5. A local sorting step that is \(\displaystyle \mathcal{O} \left( n_i/K \cdot log(n_i/K) \right)\)

  6. +
+
+

If we sum up step 1 and 3, we get

+
+
+
+\[\begin{split}\begin{equation} \label{eq1} +\begin{split} + \textrm{C}_\textrm{parallel_sort} & = \mathcal{O} \left( n_i/K \cdot log(K) + n_i/K \cdot log(n_i/K) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot \left( log(K) + log(n_i/K) \right) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot log(n_i) \right) +\end{split} +\end{equation}\end{split}\]
+
+
Theoretical scaling

\(\textrm{Speedup} = K\)

+

Experimentally, the scaling is much worse - under investigation.

+

Note: the speedup is computed by \(\textrm{Speedup} = t_s / t_p\) where \(t_s\) is the sequential time and \(t_p\) the parallel time. A speedup of \(K\) is perfect, a speedup lower than \(1\) means that sequential execution is faster.

+
+
Peak memory

Approximately \(\displaystyle \sum_{i=0}^N 2 n_i + n_{f,i}\)

+

This is the size of the input tree + the output tree. \(n_i\) is counted twice: once for the input element connectivity, once for the output NFace connectivity

+
+
Size of communications

Approximately \(\displaystyle \sum_{i=0}^N 3 n_{f,i} + n_i\) all_to_all calls

+

For each zone, one all_to_all call to sort interior faces, one to send back the faces to the NFace, one to concatenate all faces, one to concatenate all cells

+
+
Number of communication calls

Should be \(\displaystyle \mathcal{O} \left( \sum_{i=0}^N log(n_i/K) \right)\)

+

The number of communications is constant, except for the algorithm finding a balanced distribution of interior faces

+
+
Note

In practice, \(n_{f,i}\) varies from \(2 n_i\) (tet-dominant meshes) to \(3 n_i\) (hex-dominant meshes).

+
+
+
+
+

Algorithm explanation

+
maia::elements_to_ngons(cgns::tree& dist_zone, MPI_Comm comm)
+
+
+

The algorithm is divided in two steps:

+
    +
  1. Generate interior faces, insert them between exterior faces and cells. During the process, also compute the parent information (ParentElements and ParentElementsPosition) and add the CellFace connectivity to cells. All elements keep their standard element types, in particular interior faces are inserted by section type (e.g. there will be a TRI_3_interior node and a QUAD_4_interior node, not a NGon node)

  2. +
  3. Transform all sections into NGon/NFace

  4. +
+
+

Generate interior faces

+
+

Simplified algorithm

+

Let us look at a simplified sequential algorithm first:

+
1. For all element sections (3D and 2D):
+  Generate faces
+    => FaceVtx arrays (one for Tris, one for Quads)
+    => Associated Parent and ParentPosition arrays
+        (only one parent by element at this stage)
+  Interior faces are generated twice (by their two parent cells)
+  Exterior faces are generated twice (by a parent cell and a boundary face)
+
+2. For each element kind in {Tri,Quad}
+  Sort the FaceVtx array
+    (ordering: lexicographical comparison of vertices)
+  Sort the parent arrays accordingly
+    => now each face appears consecutively exactly twice
+        for interior faces,
+          the FaceVtx is always inverted between the two occurences
+        for exterior faces,
+          it depends on the normal convention
+
+Note that interior and exterior faces can be distinguised
+  by looking at the id of their parents
+
+3. For each interior face appearing twice:
+  Create an interior face section where:
+    the first FaceVtx is kept
+    the two parents are stored
+4. For each exterior face appearing twice:
+  One of the face is the original boundary section face,
+    one was generated from the joint cell
+  Send back to the original boundary face its parent face id and position
+    => store the parent information of the boundary face
+
+
+
+
+

Parallel algorithm

+

The algorithm is very similar to the sequential one. We need to modify two operations:

+
+
Sorting of the FaceVtx array (step 2)

The parallel sorting is done in three steps:

+
    +
  1. apply a partial sort std_e::sort_by_rank that will determine the rank of each FaceVtx

  2. +
  3. call an all_to_all communication step that sends each connectivity to its rank, based on the information of the previous step

  4. +
  5. sort each received FaceVtx locally

  6. +
+
+
Send back boundary parents and position to the original boundary faces (step 4)

Since the original face can be remote, this is a parallel communication operation using std_e::scatter

+
+
+
+
+

Computation of the CellFace

+

After step 2, we have all the faces exactly once, with their parent ids and parent positions. +We can then compute the CellFace of each cell section by the following algorithm:

+
For each cell section:
+  pre-allocate the CellFace array
+    (its size is n_cell_in_section * n_face_of_cell_type)
+  view it as a global distributed array
+For each unique face:
+  For each of its parent cells (could be one or two):
+    send the parent cell the id of the face and its position
+    insert the result in the CellFace array
+
+
+

As previously, the send operation uses a scatter pattern

+
+
+
+

Transform all sections into NGon/NFace

+

Thanks to the previous algorithm, we have:

+
    +
  • all exterior and interior faces with their parent information

  • +
  • the CellFace connectivity of the cell sections

  • +
+

Elements are ordered in something akin to this:

+
    +
  • boundary tris

  • +
  • boundary quads

  • +
  • internal tris

  • +
  • internal quads

  • +
  • tetras

  • +
  • pyras

  • +
  • prisms

  • +
  • hexas

  • +
+

The algorithm then just needs to:

+
    +
  • concatenate all FaceVtx of the faces into a NGon node and add a ElementStartOffset

  • +
  • concatenate all CellFace of the cells into a NFace node and add a ElementStartOffset

  • +
+
+
+
+

Design alternatives

+

The final step of the computation involves concatenating faces and cells global section by global section. This requires two heavyweight all_to_all calls. An alternative would be to concatenate locally. This would imply two trade-offs:

+
    +
  • the faces and cells would then not be globally gathered by type, and the exterior faces would not be first

  • +
  • all the PointLists (including those where GridLocation=FaceCenter) would have to be shifted

  • +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/developer_manual/developer_manual.html b/docs/1.1/developer_manual/developer_manual.html new file mode 100644 index 00000000..394be862 --- /dev/null +++ b/docs/1.1/developer_manual/developer_manual.html @@ -0,0 +1,274 @@ + + + + + + + + + + Developer Manual — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/developer_manual/logging.html b/docs/1.1/developer_manual/logging.html new file mode 100644 index 00000000..7c76caed --- /dev/null +++ b/docs/1.1/developer_manual/logging.html @@ -0,0 +1,378 @@ + + + + + + + + + + Log management — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Log management

+
+

Loggers

+

A logger is a global object where an application or a library can log to. +It can be declared with

+
from maia.utils.logging import add_logger
+
+add_logger("my_logger")
+
+
+

or with the equivalent C++ code

+
#include "std_e/logging/log.hpp"
+
+std_e::add_logger("my_logger");
+
+
+

A logger declared in Python is available in C++ and vice-versa: we do not need to create it twice.

+
+

Note

+

Loggers are available to pure C/C++ programs (in particular, test programs that do not use the Python interpreter).

+
+

It can then be referred to by its name. If we want to log a string to my_logger, we will do it like so:

+
from maia.utils.logging import log
+
+log('my_logger', 'my message')
+
+
+
#include "std_e/logging/log.hpp"
+
+std_e::log("my_logger", "my message");
+
+
+

Typically, an application will use several loggers to log messages by themes. By convention, the name of a logger in the application my_app should begin with my_app. For instance, loggers can be named: my_app, my_app-stats, my_app-errors

+

Loggers are both

+
    +
  • developper-oriented, that is, when a developper wants to output a message, he should select a logger (existing or not) encompassing the theme of the message he wants to log,

  • +
  • user-oriented, because a user can choose what logger he wants to listen to.

  • +
+
+
+

Printers

+

By itself, a logger does not do anything with the messages it receives. For that, we need to attach printers to a logger that will handle its messages.

+

For instance, we can attach a printer that will output the message to the console:

+
from maia.utils.logging import add_printer_to_logger
+add_printer_to_logger('my_logger','stdout_printer')
+
+
+

Printers are user-oriented: it is the user of the application who decides what he wants to do with the message that each logger is receiving, by attaching none or several printers to a logger.

+
+

Available printers

+
+
stdout_printer, stderr_printer

output messages to the console (respectively stdout and stderr)

+
+
mpi_stdout_printer, mpi_stderr_printer

output messages to the console, but prefix them by MPI.COMM_WORLD.Get_rank()

+
+
mpi_rank_0_stdout_printer, mpi_rank_0_stderr_printer

output messages to the console, if MPI.COMM_WORLD.Get_rank()==0

+
+
file_printer(‘my_file.extension’)

output messages to file my_file.extension

+
+
mpi_file_printer(‘my_file.extension’)

output messages to files my_file.{rk}.extension, with rk = MPI.COMM_WORLD.Get_rank()

+
+
+
+

Note

+

MPI-aware printers use MPI.COMM_WORLD rather than a local communicator. While the latter would be more correct, it would imply local instanciation of printers, at the time or after a new communicator is created. While this is certainly doable, up until now we feel that it does not worth the burden. But if the need arises, they can still be added.

+
+
+
+

Create your own printer

+

Any Python type can be used as a printer as long as it provides a log method that accepts a string argument.

+
from maia.utils.logging import add_printer_to_logger
+
+class my_printer:
+  def log(self, msg):
+    print(msg)
+
+add_printer_to_logger('my_logger',my_printer())
+
+
+
+
+
+

Configuration file

+

Loggers are associated to default printers. While they can be configured anytime in the Python scripts, most of the time, reading a configuration file at the start of the program is enough. The program will try to read a configuration file if the environment variable LOGGING_CONF_FILE is set. A logging configuration file looks like this:

+
my_app : mpi_stdout_printer
+my_app-my_theme : mpi_file_printer('my_theme.log')
+
+
+

For developpers, a logging file logging.conf with loggers and default printers is put in the build/ folder, and LOGGING_CONF_FILE is set accordingly.

+
+
+

Maia specifics

+

Maia provides 4 convenience functions that use Maia loggers

+
from maia.utils import logging as mlog
+mlog.info('info msg') # uses the 'maia' logger
+mlog.stat('stat msg') # uses the 'maia-stats' logger
+mlog.warning('warn msg') # uses the 'maia-warnings' logger
+mlog.error('error msg') # uses the 'maia-errors' logger
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/developer_manual/maia_dev/conventions.html b/docs/1.1/developer_manual/maia_dev/conventions.html new file mode 100644 index 00000000..e81d678e --- /dev/null +++ b/docs/1.1/developer_manual/maia_dev/conventions.html @@ -0,0 +1,301 @@ + + + + + + + + + + Conventions — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Conventions

+
+

Naming conventions

+
    +
  • snake_case

  • +
  • A variable holding a number of things is written n_thing. Example: n_proc, n_vtx. The suffix is singular.

  • +
  • For unit tests, when testing variable <var>, the hard-coded expected variable is named expected_<var>.

  • +
  • Usual abbreviations

    +
      +
    • elt for element

    • +
    • vtx for vertex

    • +
    • proc for process

    • +
    • sz for size (only for local variable names, not functions)

    • +
    +
  • +
  • Connectivities

    +
      +
    • cell_vtx means mesh array of cells described by their vertices (CGNS example: HEXA_8)

    • +
    • cell_face means the cells described by their faces (CGNS example: NFACE_n)

    • +
    • face_cell means for each face, the two parent cells (CGNS example: ParentElement)

    • +
    • … so on: face_vtx, edge_vtx

    • +
    • elt_vtx, elt_face…: in this case, elt can be either a cell, a face or an edge

    • +
    +
  • +
+
+
+

Other conventions

+

We try to use semi-open intervals and 0-indexed structures. This is coherent with Python, C++, and their libraries, but unfortunately not with CGNS.

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/developer_manual/maia_dev/development_workflow.html b/docs/1.1/developer_manual/maia_dev/development_workflow.html new file mode 100644 index 00000000..f69b2430 --- /dev/null +++ b/docs/1.1/developer_manual/maia_dev/development_workflow.html @@ -0,0 +1,296 @@ + + + + + + + + + + Development workflow — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Development workflow

+
+

Sub-modules

+

The Maia repository is compatible with the development process described here. It uses git submodules to ease the joint development with other repositories compatible with this organization.

+

TL;DR: configure the git repository by sourcing this file and then execute:

+
cd $MAIA_FOLDER
+git submodule update --init
+git_config_submodules
+
+
+
+
+

Launch tests

+

Tests can be launched by calling CTest, but during the development, we often want to parameterize how to run tests (which ones, number of processes, verbosity level…).

+

There is a source.sh generated in the build/ folder. It can be sourced in order to get the correct environment to launch the tests (notably, it updates LD_LIBRARY_PATH and PYTHONPATH to point to build artifacts).

+

Tests can be called with e.g.:

+
cd $PROJECT_BUILD_DIR
+source source.sh
+mpirun -np 4 external/std_e/std_e_unit_tests
+./external/cpp_cgns/cpp_cgns_unit_tests
+mpirun -np 4 test/maia_doctest_unit_tests
+mpirun -np 4 pytest $PROJECT_SRC_DIR/maia --with-mpi
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/genindex.html b/docs/1.1/genindex.html new file mode 100644 index 00000000..db29952b --- /dev/null +++ b/docs/1.1/genindex.html @@ -0,0 +1,526 @@ + + + + + + + + + + Index — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Index
  • + + +
  • + + + +
  • + +
+ + +
+
+
+
+ + +

Index

+ +
+ C + | D + | E + | F + | G + | I + | L + | M + | N + | P + | Q + | R + | S + | T + | V + | W + | Z + +
+

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

I

+ + + +
+ +

L

+ + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

P

+ + + +
+ +

Q

+ + +
+ +

R

+ + + +
+ +

S

+ + +
+ +

T

+ + +
+ +

V

+ + +
+ +

W

+ + + +
+ +

Z

+ + +
+ + + +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/index.html b/docs/1.1/index.html new file mode 100644 index 00000000..77125c75 --- /dev/null +++ b/docs/1.1/index.html @@ -0,0 +1,273 @@ + + + + + + + + + + Welcome to Maia! — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Welcome to Maia!

+

Maia is a C++ and Python library for parallel algorithms over CGNS trees: distributed IO, partitioning, data management and various kind of transformations (generation of faces, destructuration, extractions, …).

+

Maia is an open source software developed at ONERA. +Associated source repository and issue tracking are hosted on Gitlab.

+
+
+

Documentation summary

+

Quick start is the perfect page for a first overview of Maia or to retrieve the environments to use on Onera’s clusters.

+

Introduction details the extensions made to the CGNS standard in order to define parallel CGNS trees.

+

User Manual is the main part of this documentation. It describes most of the high level APIs provided by Maia.

+

Developer Manual (under construction) provides more details on some algorithms and can be consulted if you want to contribute to Maia.

+
+
+
+
+
+
+ + +
+ +
+
+ + +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/installation.html b/docs/1.1/installation.html new file mode 100644 index 00000000..d5410640 --- /dev/null +++ b/docs/1.1/installation.html @@ -0,0 +1,427 @@ + + + + + + + + + + Installation — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Installation

+
+

Prefered installation procedure

+

Maia depends on quite a few libraries of different kinds, be it system libraries like MPI, third-party libraries like HDF5, ONERA libraries like ParaDiGM and Cassiopée, git submodules (std_e…), or Python modules (mpi4py, ruamel). The prefered way of installing Maia in a coherent environment is by using the Spack package manager. A Spack recipe for Maia can be found on the ONERA Spack repository.

+
+

Installation through Spack

+
    +
  1. Source a Spack repository on your machine.

  2. +
  3. If you don’t have a Spack repository ready, you can download one with git clone https://github.com/spack/spack.git. On ONERA machines, it is advised to use the Spacky helper.

  4. +
  5. Download the ONERA Spack repository with git clone https://gitlab.onera.net/informatics/infra/onera_spack_repo.git

  6. +
  7. Tell Spack that package recipes are in onera_spack_repo by adding the following lines to $SPACK_ROOT/etc/repos.yaml:

  8. +
+
repos:
+- path/to/onera_spack_repo
+
+
+

(note that spacky does steps 3. and 4. for you)

+
    +
  1. You should be able to see the package options of Maia with spack info maia

  2. +
  3. To install Maia: spack install maia

  4. +
+
+
+

Development workflow

+

For development, it is advised to use Spack to have Maia dependencies, but then follow a typical CMake workflow with cmake/make.

+
+

Dependencies

+

To get access to Maia dependencies in your development environment, you can:

+
    +
  • Install a Spack version of Maia, source it in your development environment to get all the dependencies, then override with your own compiled version of Maia

  • +
  • Do the same, but use a Spack environment containing Maia instead of just the Maia package

  • +
  • Source a Spack environment where Maia has been removed from the environment view. This can be done by adding the following lines to the spack.yaml environement file:

  • +
+
view:
+  default:
+    exclude: ['maia']
+
+
+

This last option is cleaner because you are sure that you are not using another version of Maia (but it means you need to create or have access to such an environment view)

+
+
+

Source the build folder

+

You can develop without the need to install Maia. However, in addition to sourcing your dependencies in your development environment, you also need to source the build artifacts by:

+
cd $MAIA_BUILD_FOLDER
+source source.sh
+
+
+

The source.sh file is created by CMake and will source all Maia artifacts (dynamic libraries, python modules…)

+
+
+
+

Development workflow with submodules

+

It is often practical to develop Maia with some of its dependencies, namely:

+
    +
  • project_utils

  • +
  • std_e

  • +
  • cpp_cgns

  • +
  • paradigm

  • +
  • pytest_parallel

  • +
+

For that, you need to use git submodules. Maia submodules are located at $MAIA_FOLDER/external. To populate them, use git submodule update --init. Once done, CMake will use these versions of the dependencies. If you don’t populate the submodules, CMake will try to use the ones of your environment (for instance, the one installed by Spack).

+

We advise that you use some additional submodule configuration utilities provided in this file. In particular, you should use:

+
cd $MAIA_FOLDER
+git submodule update --init
+git_config_submodules
+
+
+

The detailed meaning of git_config_submodules and the git submodule developper workflow of Maia is presented here.

+

If you are using Maia submodules, you can filter them out from your Spack environment view like so:

+
view:
+  default:
+    exclude: ['maia','std-e','cpp-cgns','paradigm','pytest_parallel']
+
+
+
+
+
+

Manual installation procedure

+
+

Dependencies

+

Maia depends on:

+
    +
  • python3

  • +
  • MPI

  • +
  • hdf5

  • +
  • Cassiopée

  • +
  • pytest >6 (python package)

  • +
  • ruamel (python package)

  • +
  • mpi4py (python package)

  • +
+

The build process requires:

+
    +
  • Cmake >= 3.14

  • +
  • GCC >= 8 (Clang and Intel should work but no CI)

  • +
+
+

Other dependencies

+

During the build process, several other libraries will be downloaded:

+
    +
  • pybind11

  • +
  • range-v3

  • +
  • doctest

  • +
  • ParaDiGM

  • +
  • project_utils

  • +
  • std_e

  • +
  • cpp_cgns

  • +
+

This process should be transparent.

+
+
+

Optional dependencies

+

The documentation build requires:

+
    +
  • Doxygen >= 1.8.19

  • +
  • Breathe >= 4.15 (python package)

  • +
  • Sphinx >= 3.00 (python package)

  • +
+
+
+
+

Build and install

+
    +
  1. Install the required dependencies. They must be in your environment (PATH, LD_LIBRARY_PATH, PYTHONPATH).

  2. +
+
+

For pytest, you may need these lines :

+
+
pip3 install --user pytest
+pip3 install --user pytest-mpi
+pip3 install --user pytest-html
+pip3 install --user pytest_check
+pip3 install --user ruamel.yaml
+
+
+
    +
  1. Then you need to populate your external folder. You can do it with git submodule update --init

  2. +
  3. Then use CMake to build maia, e.g.

  4. +
+
SRC_DIR=<path to source repo>
+BUILD_DIR=<path to tmp build dir>
+INSTALL_DIR=<path to where you want to install Maia>
+cmake -S $SRC_DIR -B$BUILD_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR
+cd $BUILD_DIR && make -j && make install
+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/introduction/introduction.html b/docs/1.1/introduction/introduction.html new file mode 100644 index 00000000..34b42552 --- /dev/null +++ b/docs/1.1/introduction/introduction.html @@ -0,0 +1,510 @@ + + + + + + + + + + Introduction — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Introduction

+

These section introduces the core concepts of distributed and partitioned data +used in Maia, and their application to define parallel CGNS trees.

+
+

Core concepts

+
+

Dividing data

+

Global data is the complete data that describes an object. Let’s represent it as the +following ordered shapes:

+../_images/data_full.svg

Now imagine that you want to split this data into N pieces (N=3 for the next illustrations). +Among all the possibilities, we distinguishes two ways to do it:

+
    +
  1. Preserving order: we call such repartition distributed data, and we use the term block +to refer to a piece of this distributed data.

  2. +
+
+
../_images/data_dist.svg

Several distributions are possible, depending on where data is cut, but they all share the same properties:

+
+
    +
  • the original order is preserved across the distributed data,

  • +
  • each element appears in one and only one block,

  • +
  • a block can be empty as long as the global order is preserved (b).

  • +
+
+
+
    +
  1. Taking arbitrary subsets of the original data: we call such subsets partitioned data, and we use the term partition +to refer to a piece of this partitioned data.

  2. +
+
+
../_images/data_part.svg

Due to this relaxed constraint, there is much more admissible splits since we allow in our definition the following cases:

+
+
    +
  • an element can appear in several partitions, or several times within the same partition (b),

  • +
  • it is allowed that an element does not appear in a partition (c).

  • +
+
+

Such repartitions are often useful when trying to gather the elements depending on +some characteristics: on the above example, we created the partition of squared shaped elements, round shaped +elements and unfilled elements (b). Thus, some elements belong to more than one partition.

+
+

A key point is that no absolute best way of diving data: depending of what we want to do with the +data, one of the way to divide it may be more adapted. In the previous example:

+
    +
  • distributed data is fine if you want to count the number of filled shapes: you can count in each +block and then sum the result over the blocks.

  • +
  • Now assume that you want to renumber the elements depending on their shape, then on their color: +if partitioned data (b) is used, partitions 1 and 2 could independently order +their elements by color since they are already sorted by shape 1.

  • +
+
+
+

Numberings

+

In order to describe the link between our divisions and the original global data, we need to +define additional concepts.

+

For distributed data, since the original ordering is respected, the link with the global data is totally implicit: +we just need to know the number of elements in each block or, equivalently, the distribution array +of the data. This is an array of size N+1 indicating the bounds of each block. By convention, distribution +array starts at 0 and uses semi-open intervals.

+../_images/data_dist_gnum.svg

With this information, the global number of the jth element in the ith block is given by +\(\mathtt{dist[i] + j + 1}\).

+

On the contrary, for partitioned data, we have to explicitly store the link with the global data: +we use a local to global numbering array (often called LN_to_GN for short). +Each partition has its own LN_to_GN array whose size is the number of elements in the partition.

+../_images/data_part_gnum.svg

Then, the global number of the jth element in the ith partition is simply given by +\(\mathtt{LN\_to\_GN[i][j]}\).

+

For any global data, these additional informations allow to create a mapping beetween global data, partitioned +data and distributed data. Thus, it is always possible to reconstruct one of the views from another one.

+
+
+

Application to MPI parallelism

+

The concepts introduced above make all sense in the context of distributed memory computers. +In such architecture, the global data is in fact never detained by a single proc (it would be too heavy): +we always use a distributed or partitioned view of this data. +Using a good repartition is often the key of a well balanced algorithm.

+

In the distributed view, we produce as much blocks as the number of MPI processes. Each process holds +its own block, and the distribution array, of size n_rank+1, is know by each process.

+

In the partitioned view, we often produce one partition per process; but it can sometime be useful to +put several partitions on a given process. Each process holds the data associated to its partition, +including the related LN_to_GN arrays (LN_to_GN related to the other partitions +are not know by the current process).

+

The ParaDiGM library provides some low level methods to exchange data between the partitioned and +distributed views in a MPI context, with options to deal with redundant elements, create new distributions, +manage variable strides, etc.

+
+
+
+

Application to meshes

+

Until now, we have seen the definition of distributed and partitioned data for a single array +of global data. Let see how to apply these concepts to a basic CFD mesh.

+

Consider the following 12-vertices (green numbers), 6-quads (purple numbers) mesh and +assume that it is described by a cell to vertex connectivity. Thus, the mesh file defines +3 global arrays:

+
    +
  • the CoordinateX and CoordinateY arrays, each one of size 12

  • +
  • the Connectivity array of size 6*4 = 24

  • +
+../_images/full_mesh.svg

If we have 2 processes at our disposal, a parallel way to load this mesh is to +distribute all the vertex-related entities with a distribution array of [0,6,12] +and all the element-related entities with a distribution array of [0,3,6] 2:

+../_images/dist_mesh_arrays.svg

Then, the blue part of the arrays will be stored on the first process and the red part on the second process. +Looking back at the original mesh, this correspond to the following distributed meshes:

+../_images/dist_mesh.svg

with the blue entities stored on the first process, and the red ones on the second process.

+

Notice that the distributed mesh is not suited for solver computation. For instance, cells on the domain treated by the first process are not connex. Worst yet, vertex and element quantities are distributed independently. Take a look at cell 1: it is blue so it belongs to process 0. However,its vertices 9 and 10 are on process 1, this means that there coordinates are not accessible directly on process 0. For these reasons, we want to properly partition our mesh, such as this:

+../_images/part_mesh.svg../_images/part_mesh_arrays.svg

Now we have two semi-independent meshes and we can reason about each element with all its associated data +present on the same process. This partitioned view of the mesh has the two following properties:

+
+
    +
  • Coherency: every data array is addressable locally,

  • +
  • Connexity: the data represents geometrical entities that define a local subregion of the mesh.

  • +
+
+

We want to keep the link between the base mesh and its partitioned version. For that, we need to store global numbering arrays, quantity by quantity:

+../_images/dist_part_LN_to_GN.svg

For example, for sub-mesh 0, we can check that element number 3 is actually element number 6 of the original mesh. +We can also see that vertex 3 of sub-mesh 0, and vertex 1 of submesh 1 are actually the same, that is, vertex 4 of the global mesh.

+

Thanks to these global numbering, any quantity computed on the partitioned meshes (such as a vertex based field) +can then be transfered to the corresponding distributed vertex, before writting back the results.

+
+
+

Maia CGNS trees

+
+

Overview

+

Since Python/CGNS trees describe CFD meshes, we can apply the previous concepts to +define parallel trees.

+

A full tree is a tree as it is inside a CGNS file, or how it would be loaded by only one process. A full tree is global data.

+

A dist tree is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. See Distributed trees.

+

A part tree is a partial tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. See Partitioned trees.

+

A size tree is a tree in which only the size of the data is stored. A size tree is typically global data because each process needs it to know which block of data it will have to load and store.

+

([Legacy] A skeleton tree is a collective tree in which fields and element connectivities are not loaded)

+

As explained in previous section, the standard full tree does not make sense in the context of a MPI parallel +application: all the trees encountered within the execution of maia are distributed trees or partitioned trees. +The next section describe the specification of these trees.

+
+
+

Specification

+

Let us use the following tree as an example:

+../_images/tree_seq.png +

This tree is a global tree. It may appear like that on a HDF5/CGNS file, or if loaded entirely on one process as a Python/CGNS tree.

+
+

Distributed trees

+

A dist tree is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array.

+

If we distribute our tree over two processes, we would then have something like that:

+../_images/dist_tree.png +

Let us look at one of them and annotate nodes specific to the distributed tree:

+../_images/dist_tree_expl.png +

Arrays of non-constant size are distributed: fields, connectivities, PointLists. +Others (PointRanges, CGNSBase_t and Zone_t dimensions…) are of limited size and therefore replicated on all processes with virtually no memory penalty.

+

On each process, for each entity kind, a partial distribution is stored, that gives information of which block of the arrays are stored locally.

+

For example, for process 0, the distribution array of vertices of MyZone is located at MyBase/MyZone/Distribution/Vertex and is equal to [0, 9, 18]. It means that only indices in the semi-open interval [0 9) are stored by the dist tree on this process, and that the total size of the array is 18. +This partial distribution applies to arrays spaning all the vertices of the zone, e.g. CoordinateX.

+

More formally, a partial distribution related to an entity kind E is an array [start,end,total_size] of 3 integers where [start:end) is a closed/open interval giving, for all global arrays related to E, the sub-array that is stored locally on the distributed tree, and total_size is the global size of the arrays related to E.

+

The distributed entities are:

+
+
Vertices and Cells

The partial distribution are stored in Distribution/Vertex and Distribution/Cell nodes at the level of the Zone_t node.

+

Used for example by GridCoordinates_t and FlowSolution_t nodes if they do not have a PointList (i.e. if they span the entire vertices/cells of the zone)

+
+
Quantities described by a PointList or PointRange

The partial distribution is stored in a Distribution/Index node at the level of the PointList/PointRange

+

For example, ZoneSubRegion_t and BCDataSet_t nodes.

+

If the quantity is described by a PointList, then the PointList itself is distributed the same way (in contrast, a PointRange is fully replicated across processes because it is lightweight)

+
+
Connectivities

The partial distribution is stored in a Distribution/Element node at the level of the Element_t node. Its values are related to the elements, not the vertices of the connectivity array.

+

If the element type is heterogenous (NGon, NFace or MIXED) a Distribution/ElementConnectivity is also present, and this partial distribution is related to the ElementConnectivity array.

+
+
+
+

Note

+

A distributed tree object is not norm-compliant since most of its arrays are partial: on the previous example, +CoordinateX array on rank 0 has a length of 9 when MyZone declares 18 vertices. +However, the union of all the distributed tree objects represents a norm-compliant CGNS tree.

+
+
+
+

Partitioned trees

+

A part tree is a partial CGNS tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process.

+

If we take the global tree from before and partition it, we may get the following tree:

+../_images/part_tree.png +

If we annotate the first one:

+../_images/part_tree_expl.png +

A part tree is just a regular, norm-compliant tree with additional information (in the form of GlobalNumbering nodes) that keeps the link with the unpartitioned tree it comes from. Notice that the tree structure is not necessarily the same across all processes.

+

The GlobalNumbering nodes are located at the same positions that the Distribution nodes were in the distributed tree.

+

A GlobalNumbering contains information to link an entity in the partition to its corresponding entity in the original tree. For example, the element section Hexa has a global numbering array of value [3 4]. It means:

+
    +
  • Since it is an array of size 2, there is 2 elements in this section (which is confirmed by the ElementRange) ,

  • +
  • The first element was the element of id 3 in the original mesh,

  • +
  • The second element was element 4 in the original mesh.

  • +
+
+
+

Naming conventions

+

When partitioning, some nodes are split, so there is convention to keep track of the fact they come from the same original node:

+
    +
  • Zone_t nodes : MyZone is split in MyZone.PX.NY where X is the rank of the process, and Y is the id of the zone on process X.

  • +
  • Splitable nodes (notably GC_t) : MyNode is split in MyNode.N. They appear in the following scenario:

    +
      +
    • We partition for 3 processes

    • +
    • Zone0 is connected to Zone1 through GridConnectivity_0_to_1

    • +
    • Zone0 is not split (but goes to process 0 and becomes Zone0.P0.N0). Zone1 is split into Zone1.P1.N0 and Zone1.P2.N0. Then GridConnectivity_0_to_1 of Zone0 must be split into GridConnectivity_0_to_1.1 and GridConnectivity_0_to_1.2.

    • +
    +
  • +
+

Note that partitioning may induce new GC_t internal to the original zone being splitted. Their name is implementation-defined and those nodes do not have a GlobalNumbering since they did not exist in the original mesh.

+
+
+
+

Maia trees

+

A CGNS tree is said to be a Maia tree if it has the following properties:

+
    +
  • For each unstructured zone, the ElementRange of all Elements_t sections

    +
      +
    • are contiguous

    • +
    • are ordered by ascending dimensions (i.e. edges come first, then faces, then cells)

    • +
    • the first section starts at 1

    • +
    • there is at most one section by element type (e.g. not possible to have two QUAD_4 sections)

    • +
    +
  • +
+

Notice that this is property is required by some functions of Maia, not all of them!

+

A Maia tree may be a global tree, a distributed tree or a partitioned tree.

+

Footnotes

+
+
1
+

Actually, such algorithm could be written for distributed data, but is less intuitive and require more knowledge of what +if happening on the other blocks.

+
+
2
+

Other distributions are possible : we could, for example, affect all the CoordinateX array on the first process (using the distribution array [0,12,12]) and the CoordinateY array on the second, but we would have to manage a different distribution for each array.

+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/license.html b/docs/1.1/license.html new file mode 100644 index 00000000..fd9b5fee --- /dev/null +++ b/docs/1.1/license.html @@ -0,0 +1,679 @@ + + + + + + + + + + License — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

License

+
+

Mozilla Public License Version 2.0

+
+
+

1. Definitions

+
+
1.1. “Contributor”

means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software.

+
+
1.2. “Contributor Version”

means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor’s Contribution.

+
+
1.3. “Contribution”

means Covered Software of a particular Contributor.

+
+
1.4. “Covered Software”

means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof.

+
+
1.5. “Incompatible With Secondary Licenses”

means

+
+
+
    +
  • +
    (a) that the initial Contributor has attached the notice described

    in Exhibit B to the Covered Software; or

    +
    +
    +
  • +
  • +
    (b) that the Covered Software was made available under the terms of

    version 1.1 or earlier of the License, but not also under the +terms of a Secondary License.

    +
    +
    +
  • +
+
+
1.6. “Executable Form”

means any form of the work other than Source Code Form.

+
+
1.7. “Larger Work”

means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software.

+
+
1.8. “License”

means this document.

+
+
1.9. “Licensable”

means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License.

+
+
1.10. “Modifications”

means any of the following:

+
+
+
    +
  • +
    (a) any file in Source Code Form that results from an addition to,

    deletion from, or modification of the contents of Covered +Software; or

    +
    +
    +
  • +
  • +
    (b) any new file in Source Code Form that contains any Covered

    Software.

    +
    +
    +
  • +
+
+
1.11. “Patent Claims” of a Contributor

means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version.

+
+
1.12. “Secondary License”

means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses.

+
+
1.13. “Source Code Form”

means the form of the work preferred for making modifications.

+
+
1.14. “You” (or “Your”)

means an individual or a legal entity exercising rights under this +License. For legal entities, “You” includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, “control” means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity.

+
+
+
+
+

2. License Grants and Conditions

+
+

2.1. Grants

+

Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license:

+
    +
  • +
    (a) under intellectual property rights (other than patent or trademark)

    Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and

    +
    +
    +
  • +
  • +
    (b) under Patent Claims of such Contributor to make, use, sell, offer

    for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version.

    +
    +
    +
  • +
+
+
+

2.2. Effective Date

+

The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution.

+
+
+

2.3. Limitations on Grant Scope

+

The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor:

+
    +
  • +
    (a) for any code that a Contributor has removed from Covered Software;

    or

    +
    +
    +
  • +
  • +
    (b) for infringements caused by: (i) Your and any other third party’s

    modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or

    +
    +
    +
  • +
  • +
    (c) under Patent Claims infringed by Covered Software in the absence of

    its Contributions.

    +
    +
    +
  • +
+

This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4).

+
+
+

2.4. Subsequent Licenses

+

No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3).

+
+
+

2.5. Representation

+

Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License.

+
+
+

2.6. Fair Use

+

This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents.

+
+
+

2.7. Conditions

+

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1.

+
+
+
+

3. Responsibilities

+
+

3.1. Distribution of Source Form

+

All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients’ rights in the Source Code +Form.

+
+
+

3.2. Distribution of Executable Form

+

If You distribute Covered Software in Executable Form then:

+
    +
  • +
    (a) such Covered Software must also be made available in Source Code

    Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and

    +
    +
    +
  • +
  • +
    (b) You may distribute such Executable Form under the terms of this

    License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients’ rights in the Source Code Form under this License.

    +
    +
    +
  • +
+
+
+

3.3. Distribution of a Larger Work

+

You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s).

+
+
+

3.4. Notices

+

You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies.

+
+
+

3.5. Application of Additional Terms

+

You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction.

+
+
+
+

4. Inability to Comply Due to Statute or Regulation

+

If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it.

+
+
+

5. Termination

+

5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice.

+

5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate.

+

5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination.

+
+
+

6. Disclaimer of Warranty

+
+

Covered Software is provided under this License on an “as is” +basis, without warranty of any kind, either expressed, implied, or +statutory, including, without limitation, warranties that the +Covered Software is free of defects, merchantable, fit for a +particular purpose or non-infringing. The entire risk as to the +quality and performance of the Covered Software is with You. +Should any Covered Software prove defective in any respect, You +(not any Contributor) assume the cost of any necessary servicing, +repair, or correction. This disclaimer of warranty constitutes an +essential part of this License. No use of any Covered Software is +authorized under this License except under this disclaimer.

+
+
+
+

7. Limitation of Liability

+
+

Under no circumstances and under no legal theory, whether tort +(including negligence), contract, or otherwise, shall any +Contributor, or anyone who distributes Covered Software as +permitted above, be liable to You for any direct, indirect, +special, incidental, or consequential damages of any character +including, without limitation, damages for lost profits, loss of +goodwill, work stoppage, computer failure or malfunction, or any +and all other commercial damages or losses, even if such party +shall have been informed of the possibility of such damages. This +limitation of liability shall not apply to liability for death or +personal injury resulting from such party’s negligence to the +extent applicable law prohibits such limitation. Some +jurisdictions do not allow the exclusion or limitation of +incidental or consequential damages, so this exclusion and +limitation may not apply to You.

+
+
+
+

8. Litigation

+

Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party’s ability to bring +cross-claims or counter-claims.

+
+
+

9. Miscellaneous

+

This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor.

+
+
+

10. Versions of the License

+
+

10.1. New Versions

+

Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number.

+
+
+

10.2. Effect of New Versions

+

You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward.

+
+
+

10.3. Modified Versions

+

If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License).

+
+
+

10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses

+

If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached.

+
+
+
+

Exhibit A - Source Code Form License Notice

+
+

This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/.

+
+

If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice.

+

You may add additional accurate notices of copyright ownership.

+
+
+

Exhibit B - “Incompatible With Secondary License” Notice

+
+

This Source Code Form is “Incompatible With Secondary Licenses”, as +defined by the Mozilla Public License, v. 2.0.

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/objects.inv b/docs/1.1/objects.inv new file mode 100644 index 00000000..e326e13a Binary files /dev/null and b/docs/1.1/objects.inv differ diff --git a/docs/1.1/quick_start.html b/docs/1.1/quick_start.html new file mode 100644 index 00000000..0afcaf63 --- /dev/null +++ b/docs/1.1/quick_start.html @@ -0,0 +1,438 @@ + + + + + + + + + + Quick start — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Quick start

+
+

Environnements

+

Maia is now distributed in elsA releases (since v5.2.01) !

+

If you want to try the latest features, we provide ready-to-go environments including Maia and its dependencies on the following clusters:

+

Spiro-EL8

+

This is the recommended environment for standalone usage of Maia. It works with intel mpi library (2021) +and python version 3.9.

+
source /scratchm/sonics/dist/source.sh --env maia
+module load maia/dev-default
+
+
+

If you want to use Maia within the standard Spiro environment, the next installation is compatible with +the socle socle-cfd/5.0-intel2120-impi:

+
module use --append /scratchm/sonics/usr/modules/
+module load maia/dev-dsi-cfd5
+
+
+

Note that this is the environment used by elsA for its production spiro-el8_mpi.

+

Sator

+

Similarly, Maia installation are available in both the self maintained and standard socle +on Sator cluster. Sator’s version is compiled with support of large integers.

+
# Versions based on self compiled tools
+source /tmp_user/sator/sonics/dist/source.sh --env maia
+module load maia/dev-default
+
+# Versions based on socle-cfd compilers and tools
+module use --append /tmp_user/sator/sonics/usr/modules/
+module load maia/dev-dsi-cfd5
+
+
+

If you prefer to build your own version of Maia, see Installation section.

+
+
+

Supported meshes

+

Maia supports CGNS meshes from version 4.2, meaning that polyhedral connectivities (NGON_n, NFACE_n +and MIXED nodes) must have the ElementStartOffset node.

+

Former meshes can be converted with the (sequential) maia_poly_old_to_new script included +in the $PATH once the environment is loaded:

+
$> maia_poly_old_to_new mesh_file.hdf
+
+
+

The opposite maia_poly_new_to_old script can be used to put back meshes +in old conventions, insuring compatibility with legacy tools.

+
+

Warning

+

CGNS databases should respect the SIDS. +The most commonly observed non-compliant practices are:

+
    +
  • Empty DataArray_t (of size 0) under FlowSolution_t containers.

  • +
  • 2D shaped (N1,N2) DataArray_t under BCData_t containers. +These arrays should be flat (N1xN2,).

  • +
  • Implicit BCDataSet_t location for structured meshes: if GridLocation_t +and PointRange_t of a given BCDataSet_t differs from the +parent BC_t node, theses nodes should be explicitly defined at BCDataSet_t +level.

  • +
+

Several non-compliant practices can be detected with the cgnscheck utility. Do not hesitate +to check your file if Maia is unable to read it.

+
+

Note also that ADF files are not supported; CGNS files should use the HDF binary format. ADF files can +be converted to HDF thanks to cgnsconvert.

+
+
+

Highlights

+
+

Tip

+

Download sample files of this section: +S_twoblocks.cgns, +U_ATB_45.cgns

+
+

Daily user-friendly pre & post processing

+

Maia provides simple Python APIs to easily setup pre or post processing operations: +for example, converting a structured tree into an unstructured (NGon) tree +is as simple as

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia
+
+tree_s = maia.io.file_to_dist_tree('S_twoblocks.cgns', comm)
+tree_u = maia.algo.dist.convert_s_to_ngon(tree_s, comm)
+maia.io.dist_tree_to_file(tree_u, 'U_twoblocks.cgns', comm)
+
+
+_images/qs_basic.png +

In addition of being parallel, the algorithms are as much as possible topologic, meaning that +they do not rely on a geometric tolerance. This also allow us to preserve the boundary groups +included in the input mesh (colored on the above picture).

+

Building efficient workflows

+

By chaining this elementary blocks, you can build a fully parallel advanced workflow +running as a single job and minimizing file usage.

+

In the following example, we load an angular section of the +ATB case, +duplicate it to a 180° case, split it, and perform some slices and extractions.

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia.pytree as PT
+import maia
+
+# Read the file. Tree is distributed
+dist_tree = maia.io.file_to_dist_tree('U_ATB_45.cgns', comm)
+
+# Duplicate the section to a 180° mesh
+# and merge all the blocks into one
+opposite_jns = [['Base/bump_45/ZoneGridConnectivity/matchA'],
+                ['Base/bump_45/ZoneGridConnectivity/matchB']]
+maia.algo.dist.duplicate_from_periodic_jns(dist_tree,
+    ['Base/bump_45'], opposite_jns, 22, comm)
+maia.algo.dist.merge_connected_zones(dist_tree, comm)
+
+# Split the mesh to have a partitioned tree
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Now we can call some partitioned algorithms
+maia.algo.part.compute_wall_distance(part_tree, comm, 
+    families=['WALL'], point_cloud='Vertex')
+extract_tree = maia.algo.part.extract_part_from_bc_name(part_tree, "wall", comm)
+slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0], comm,
+      containers_name=['WallDistance'])
+
+# Merge extractions in a same tree in order to save it
+base = PT.get_child_from_label(slice_tree, 'CGNSBase_t')
+PT.set_name(base, f'PlaneSlice')
+PT.add_child(extract_tree, base)
+maia.algo.pe_to_nface(dist_tree,comm)
+
+extract_tree_dist = maia.factory.recover_dist_tree(extract_tree, comm)
+maia.io.dist_tree_to_file(extract_tree_dist, 'ATB_extract.cgns', comm)
+
+
+_images/qs_workflow.png +

The above illustration represents the input mesh (gray volumic block) and the +extracted surfacic tree (plane slice and extracted wall BC). Curved lines are +the outline of the volumic mesh after duplication.

+

Compliant with the pyCGNS world

+

Finally, since Maia uses the standard CGNS/Python mapping, +you can set up applications involving multiple python packages: +here, we create and split a mesh with maia, but we then call Cassiopee functions +to compute the gradient of a field on each partition.

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia
+import Transform.PyTree as CTransform
+import Converter.PyTree as CConverter
+import Post.PyTree      as CPost
+
+dist_tree = maia.factory.generate_dist_block([101,6,6], 'TETRA_4', comm)
+CTransform._scale(dist_tree, [5,1,1], X=(0,0,0))
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+CConverter._initVars(part_tree, '{Field}=sin({nodes:CoordinateX})')
+part_tree = CPost.computeGrad(part_tree, 'Field')
+
+maia.transfer.part_tree_to_dist_tree_all(dist_tree, part_tree, comm)
+maia.io.dist_tree_to_file(dist_tree, 'out.cgns', comm)
+
+
+_images/qs_pycgns.png +

Be aware that other tools often expect to receive geometrically +consistent data, which is why we send the partitioned tree to +Cassiopee functions. The outline of this partitions (using 4 processes) +are materialized by the white lines on the above figure.

+
+
+

Resources and Troubleshouting

+

The user manual describes most of the high level APIs provided by Maia. +If you want to be comfortable with the underlying concepts of distributed and +partitioned trees, have a look at the introduction section.

+

The user manual is illustrated with basic examples. Additional +test cases can be found in +the sources.

+

Issues can be reported on +the gitlab board +and help can also be asked on the dedicated +Element room.

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/related_projects.html b/docs/1.1/related_projects.html new file mode 100644 index 00000000..bf8351d0 --- /dev/null +++ b/docs/1.1/related_projects.html @@ -0,0 +1,282 @@ + + + + + + + + + + Related projects — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/releases/release_notes.html b/docs/1.1/releases/release_notes.html new file mode 100644 index 00000000..47c0845e --- /dev/null +++ b/docs/1.1/releases/release_notes.html @@ -0,0 +1,313 @@ + + + + + + + + + + Release notes — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Release notes

+

This page contains information about what has changed in each new version of Maia.

+
+

v1.1 (May 2023)

+
+

New Features

+
    +
  • Algo module: generate (periodic) 1to1 GridConnectivity between selected BC or GC

  • +
  • Factory module: generate 2D spherical meshes and points clouds

  • +
+
+
+

Feature improvements

+
    +
  • generate_dist_block: enable generation of structured meshes

  • +
  • partitioning: enable split of 2D (NGON/Elts) and 1D (Elts) meshes

  • +
  • partitioning: copy AdditionalFamilyName and ReferenceState from BCs to the partitions

  • +
  • compute_face_center : manage structured meshes

  • +
  • merge_zones: allow wildcards in zone_paths

  • +
  • isosurface: recover volumic GCs on surfacic tree (as BCs)

  • +
  • transfer (part->dist): manage BC/BCDataSet created on partitions for structured meshes

  • +
+
+
+

Fixes

+
    +
  • convert_elements_to_ngon: prevent a memory error & better management of 2D meshes

  • +
  • isosurface: improve robustness of edge reconstruction

  • +
  • partitioning: fix split of structured GCs and BCDataSet

  • +
  • merge_zone: fix a bug occurring when FamilyName appears under some BC_t nodes

  • +
+
+
+

Advanced users / devs

+
    +
  • use new pytest_parallel module

  • +
  • transfer (part->dist): add user callback to reduce shared entities

  • +
+
+
+
+

v1.0 (March 2023)

+

First release of Maia !

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/search.html b/docs/1.1/search.html new file mode 100644 index 00000000..2fd38b3c --- /dev/null +++ b/docs/1.1/search.html @@ -0,0 +1,268 @@ + + + + + + + + + + Search — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Search
  • + + +
  • + +
  • + +
+ + +
+
+
+
+ + + + +
+ +
+ +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/searchindex.js b/docs/1.1/searchindex.js new file mode 100644 index 00000000..377cb41e --- /dev/null +++ b/docs/1.1/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["developer_manual/algo_description","developer_manual/algo_description/elements_to_ngons","developer_manual/developer_manual","developer_manual/logging","developer_manual/maia_dev/conventions","developer_manual/maia_dev/development_workflow","index","installation","introduction/introduction","license","quick_start","related_projects","releases/release_notes","user_manual/algo","user_manual/config","user_manual/factory","user_manual/io","user_manual/transfer","user_manual/user_manual"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["developer_manual/algo_description.rst","developer_manual/algo_description/elements_to_ngons.rst","developer_manual/developer_manual.rst","developer_manual/logging.rst","developer_manual/maia_dev/conventions.rst","developer_manual/maia_dev/development_workflow.rst","index.rst","installation.rst","introduction/introduction.rst","license.rst","quick_start.rst","related_projects.rst","releases/release_notes.rst","user_manual/algo.rst","user_manual/config.rst","user_manual/factory.rst","user_manual/io.rst","user_manual/transfer.rst","user_manual/user_manual.rst"],objects:{"":[[15,0,1,"","dump_pdm_output"],[15,0,1,"","graph_part_tool"],[15,0,1,"","part_interface_loc"],[15,0,1,"","preserve_orientation"],[15,0,1,"","reordering"],[15,0,1,"","zone_to_parts"]],"maia.algo":[[13,1,1,"","nface_to_pe"],[13,1,1,"","pe_to_nface"],[13,1,1,"","transform_affine"]],"maia.algo.dist":[[13,1,1,"","conformize_jn_pair"],[13,1,1,"","connect_1to1_families"],[13,1,1,"","convert_elements_to_mixed"],[13,1,1,"","convert_elements_to_ngon"],[13,1,1,"","convert_mixed_to_elements"],[13,1,1,"","convert_s_to_u"],[13,1,1,"","duplicate_from_rotation_jns_to_360"],[13,1,1,"","generate_jns_vertex_list"],[13,1,1,"","merge_connected_zones"],[13,1,1,"","merge_zones"],[13,1,1,"","merge_zones_from_family"],[13,1,1,"","ngons_to_elements"],[13,1,1,"","rearrange_element_sections"],[13,1,1,"","redistribute_tree"]],"maia.algo.part":[[13,1,1,"","centers_to_nodes"],[13,1,1,"","compute_cell_center"],[13,1,1,"","compute_edge_center"],[13,1,1,"","compute_face_center"],[13,1,1,"","compute_wall_distance"],[13,1,1,"","extract_part_from_bc_name"],[13,1,1,"","extract_part_from_zsr"],[13,1,1,"","find_closest_points"],[13,1,1,"","interpolate_from_part_trees"],[13,1,1,"","iso_surface"],[13,1,1,"","localize_points"],[13,1,1,"","plane_slice"],[13,1,1,"","spherical_slice"]],"maia.algo.seq":[[13,1,1,"","enforce_ngon_pe_local"],[13,1,1,"","poly_new_to_old"],[13,1,1,"","poly_old_to_new"]],"maia.factory":[[15,1,1,"","distribute_tree"],[15,1,1,"","generate_dist_block"],[15,1,1,"","generate_dist_points"],[15,1,1,"","generate_dist_sphere"],[15,1,1,"","partition_dist_tree"],[15,1,1,"","recover_dist_tree"]],"maia.factory.partitioning":[[15,1,1,"","compute_balanced_weights"],[15,1,1,"","compute_nosplit_weights"],[15,1,1,"","compute_regular_weights"]],"maia.io":[[16,1,1,"","dist_tree_to_file"],[16,1,1,"","file_to_dist_tree"],[16,1,1,"","part_tree_to_file"],[16,1,1,"","read_tree"],[16,1,1,"","write_tree"],[16,1,1,"","write_trees"]],"maia.transfer":[[17,1,1,"","dist_tree_to_part_tree_all"],[17,1,1,"","dist_tree_to_part_tree_only_labels"],[17,1,1,"","dist_zone_to_part_zones_all"],[17,1,1,"","dist_zone_to_part_zones_only"],[17,1,1,"","part_tree_to_dist_tree_all"],[17,1,1,"","part_tree_to_dist_tree_only_labels"],[17,1,1,"","part_zones_to_dist_zone_all"],[17,1,1,"","part_zones_to_dist_zone_only"]]},objnames:{"0":["py","attribute","Python attribute"],"1":["py","function","Python function"]},objtypes:{"0":"py:attribute","1":"py:function"},terms:{"0":[1,3,4,8,10,13,14,15,16,17],"00":7,"01":10,"1":[1,7,8,10,13,15],"10":[8,13,15,16],"101":10,"10m":15,"11":[9,13],"12":[8,9],"13":9,"14":[7,9,13],"15":7,"170":13,"18":8,"180":[10,13],"19":7,"1d":12,"1e":13,"1to1":[12,13],"2":[1,8,10,13,15],"20":[13,15],"2021":10,"21":13,"22":10,"24":8,"25":13,"2d":[1,10,12,13,15],"3":[1,7,8,10,13,15],"30":9,"375":15,"3d":[1,13,15],"4":[1,3,5,7,8,10,13],"45":13,"5":[10,13,15],"50":9,"6":[7,8,10,13],"60":9,"625":15,"8":7,"9":[8,10],"case":[4,8,9,10,13,15,16],"cassiop\u00e9":7,"class":[3,15],"default":[3,7,10,13,14,15,16],"do":[3,7,8,9,10,16,17,18],"final":[1,9,10],"float":[13,15],"function":[1,3,4,8,10,13,14,15,16,17,18],"import":[1,3,9,10,13,15,16,17],"int":15,"long":[3,8],"new":[3,8,13,18],"return":[13,15,16,18],"short":8,"true":[1,13,15,16],"try":[3,4,7,8,10,13],"var":4,"while":3,A:[1,3,4,7,8,11,13,18],As:[1,8],Be:[10,16],But:3,By:[3,8,10],For:[1,3,4,7,8,9,13,15,16,17],If:[1,3,7,8,9,10,13,15,16],In:[1,7,8,9,10,13,15,16,17],It:[3,5,6,7,8,10,17],Its:8,No:[9,14],On:[7,8],One:1,Such:[8,9],The:[1,3,4,5,7,8,9,10,11,13,14,15,16,17],Their:8,Then:[7,8],There:5,These:[8,10,13,17],To:7,With:[1,8],_:1,_all:17,_exchange_field:13,_gn:8,_initvar:10,_onli:17,_scale:10,_to:8,_unmatch:13,abbrevi:4,abil:9,abl:[7,9],abort:14,about:[8,12],abov:[8,9,10,13,15],absenc:9,absolut:[8,9,15],accept:[3,13,16],access:[7,8,13,18],accord:[13,15,17],accordingli:[1,3,13],account:1,accur:9,achiev:16,across:[8,15,17],action:9,actual:[8,13],ad:[3,7,13,16],adapt:8,add:[1,9,12,13],add_child:10,add_logg:3,add_printer_to_logg:3,addit:[7,8,10,13,16,17],addition:9,additionalfamilynam:[12,13],address:8,adf:10,admiss:[8,13,15],admit:13,advanc:10,advic:13,advis:7,affect:[8,9,15],affero:9,affin:13,after:[1,3,9,10],against:9,agre:9,agreement:9,akin:1,algo:[1,10,12,18],algorithm:[2,6,8,10,18],all:[7,8,9,10,13,14,15,16,17],all_to_al:1,alleg:9,alloc:1,allow:[8,9,10,12,13,14,17,18],alon:9,alreadi:8,also:[1,7,8,9,10,13,14,16,17],alter:9,altern:15,alwai:[1,8,13],among:8,an:[1,3,4,6,7,8,9,10,13,14,15],analysi:11,angl:13,angular:[10,13],ani:[3,8,9,13,17],annot:8,anoth:[7,8,18],anyon:9,anyth:3,anytim:3,api:[6,10,17],apparatu:9,appear:[1,8,12,13],append:10,appli:[1,8,9,13,18],applic:[3,10,14,18],apply_to_field:13,approxim:1,ar:[1,3,6,7,8,9,10,13,14,15,16,17,18],arang:13,arbitrari:8,architectur:8,argument:[3,13,15],aris:3,arithmet:13,arrai:[1,4,8,10,13,15],artifact:[5,7],ascend:[8,13],ask:10,assert:[9,13,15,17],associ:[1,3,6,8,15],assum:[8,9,17],atb:10,atb_extract:10,attach:[3,9],attempt:9,author:9,auto:13,automat:[9,14,15,16],avail:[9,10,13,14,15,18],averag:13,avoid:14,awar:[3,10,16],ax:13,b:[7,8,13],back:[1,8,9,10],balanc:[1,8,13,15],bar:15,base:[1,8,10,13,15],basea:13,baseb:13,basenam:13,basi:9,basic:[8,10,13,15],bc:[10,12,13,16],bc_name:13,bc_t:[10,12,13],bcdata_t:10,bcdataset:[12,13,15,17],bcdataset_t:[8,10,13,17],becaus:[3,7,8],becom:[8,9,13],been:[7,9,13,15,17],beetween:8,befor:8,begin:[1,3,13],behalf:9,behaviour:[13,14],being:[8,10],believ:9,belong:[8,13],below:[1,16],benefici:9,best:8,better:[12,13],between:[1,8,12,13,17,18],binari:10,blk1:13,blk2:13,block:[8,10,13,15],blue:8,bnd2:17,board:10,bool:[13,15,16],both:[3,10,13,15,18],bound:8,boundari:[1,10,13,15],breath:7,bring:[9,13],brought:9,bucket:1,bug:12,build:[3,5,10],build_dir:7,bump_45:10,burden:3,busi:9,c:[1,3,4,6,8,9,13],cacheblock:15,cacheblocking2:15,call:[1,5,8,10,13,14,16,18],callback:12,can:[1,3,4,5,6,7,8,9,10,13,14,15,16],cartesian:[13,15],cassiope:[10,11,16],caus:9,cconvert:10,ccx:13,ccy:13,ccz:13,cd:[5,7],cdot:[1,13],cell:[1,4,8,13,15],cell_cent:13,cell_fac:4,cell_renum_method:15,cell_vtx:4,cellcent:13,center:13,centers_to_nod:13,centertonod:13,certainli:3,cfd5:10,cfd:[8,10,11,18],cgn:[1,4,6,7,10,13,15,16,17],cgns_elmt_nam:15,cgns_io_tre:16,cgnsbase_t:[1,8,10,15],cgnscheck:10,cgnsconvert:10,cgnsname:15,cgnsnode:15,cgnstree:[13,15,16,17],cgnstree_t:1,chain:[10,18],chang:[12,14,18],charact:9,characterist:8,charg:9,check:[8,10],choic:[9,13],choos:[3,9,13],choosen:13,chosen:13,ci:7,circular:13,circumst:9,claim:9,clang:7,cleaner:7,clear:9,clone:7,close:8,closest:13,closestpoint:13,cloud:[12,13,15],cluster:[6,10],cmake:7,code:[3,4],coher:[4,7,8],collect:[1,8],color:[8,10],com:7,combin:9,come:[8,9,13],comfort:10,comm:[1,10,13,15,16,17],comm_world:[1,3,10,13,14,15,16,17],commerci:9,common:9,commonli:10,commun:[1,3,13,14,15,16,17],comparison:1,compat:[5,10],compil:[7,10,16],complet:[8,9],complianc:9,compliant:[8,9,10],compon:13,comput:[8,9,10,11,13,15,16,18],compute_balanced_weight:15,compute_cell_cent:13,compute_edge_cent:13,compute_face_cent:[12,13],compute_nosplit_weight:15,compute_regular_weight:15,compute_wall_dist:[10,13],computegrad:10,concaten:1,concatenate_jn:13,concept:10,concern:9,condit:15,conf:[3,14],configur:[5,7],confirm:8,conflict:9,conform:13,conformize_jn_pair:13,connect:[1,4,8,10,15],connect_1to1_famili:13,connex:8,consecut:1,consequ:15,consequenti:9,consid:[8,13,17],consist:[1,10,11,18],consol:3,constant:[1,8],constitut:9,constraint:[8,13],constru:9,construct:6,consult:6,contain:[1,7,8,9,10,12,13,15,16,17],container_nam:13,containers_nam:[10,13],content:9,context:8,contigu:8,contract:9,contrari:8,contrast:8,contribut:[6,9,13],contributor:9,control:[9,13,15,16],convei:9,conveni:[3,15,17],convent:[1,2,3,10,13],convers:18,convert:[10,16],convert_elements_to_mix:13,convert_elements_to_ngon:[1,12,13],convert_mixed_to_el:13,convert_s_to_ngon:[10,13],convert_s_to_u:13,coordin:[8,13,15],coordinatei:[8,16],coordinatex:[8,10],coordinatez:16,copi:[9,12,13,15],copyright:9,correct:[3,5,9,13],correspond:[8,13,15,17,18],cost:[1,9],could:[1,8,18],count:[1,8],counter:9,court:9,cover:9,cpost:10,cpp:7,cpp_cgn:[5,7],cpp_cgns_unit_test:5,crazi:15,creat:[1,7,8,9,10,12,13,14,15,16,17],create_extractor_from_zsr:13,create_interpolator_from_part_tre:13,creation:9,cross:[9,13],cross_domain:13,ctest:5,ctransform:10,current:[8,15],curv:10,custom:15,cut:8,cuthil:15,cz:13,d:13,dai:9,daili:10,damag:9,data:[6,10,11,15,16,17,18],dataarrai:16,dataarray_t:[10,17],databas:10,datai:17,datax:17,dataz:17,dcmake_install_prefix:7,deadlock:14,deal:[8,9,16],death:9,debug:[15,16],decid:3,declar:[3,8],declaratori:9,decreas:13,dedic:10,def:3,defect:9,defend:9,defin:[6,8,9,10,13,15,17],definit:[8,17],delet:9,densiti:17,depend:[1,8,10,13],describ:[1,4,5,6,8,9,10,13,15,18],descript:[2,9,14,15],desir:9,destin:18,destructur:[6,13],detail:[0,6,7,9,14],detain:8,detect:[10,13],determin:1,dev:10,develop:[6,11],developp:[3,7],dic:13,dict:[13,15],dictionari:[15,17],dictionnari:[13,15],did:8,differ:[7,8,9,10,15,18],dimens:[8,13,15],dimension:15,dir:7,direct:[9,15],directli:[8,9,14,18],directori:9,dirichletdata:17,disabl:[13,14],disable_mpi_excepthook:14,discretedata:15,discretedata_t:[13,17],disk:18,dispatch:15,displai:9,displaystyl:1,dispos:8,dist:[1,8,10,12,13,18],dist_tre:[1,10,13,15,16,17],dist_tree_:13,dist_tree_bck:15,dist_tree_elt:1,dist_tree_gath:13,dist_tree_ini:13,dist_tree_src:13,dist_tree_tgt:13,dist_tree_to_fil:[10,16],dist_tree_to_part_tree_al:17,dist_tree_to_part_tree_only_label:17,dist_tree_u:13,dist_zon:[1,17],dist_zone_to_part_zones_al:17,dist_zone_to_part_zones_onli:17,distanc:[13,18],distinct:13,distinguis:1,distinguish:[8,9],distribut:[1,6,10,15,17,18],distribute_tre:15,distributor:9,disttre:[13,17],disttree_:13,dive:8,divid:[1,15,16],divis:8,doabl:3,doctest:7,doctrin:9,document:[7,9,13,15,16],doe:[3,7,8,9,13],domain:[8,13],domin:1,don:7,done:[1,7,13],download:[7,10],doxygen:7,dr:5,drafter:9,driver:16,dsi:10,dtype:13,due:8,dump:15,dump_pdm_output:15,duplic:[10,13],duplicate_from_periodic_jn:10,duplicate_from_rotation_jns_to_360:13,dure:[1,5,7],dynam:[7,11,14],e:[1,5,7,8,13,14,17,18],each:[1,3,4,8,9,10,12,13,15,16,17,18],earlier:9,eas:5,easiest:[14,16],easili:10,edg:[4,8,12,13,15],edge_cent:13,edge_length:15,edge_vtx:4,effect:[14,15],effici:10,either:[4,9,13,18],el8:10,el8_mpi:10,element:[1,4,8,10,13,15],element_t:[8,13],elementari:10,elementconnect:[1,8],elementrang:[8,13],elements_t:[8,13,15],elements_to_ngon:0,elementstartoffset:[1,10],elementtyp:13,elev:13,els:15,elsa:[10,11],elt:[4,12,13],elt_fac:4,elt_typ:13,elt_vtx:4,empti:[8,10,13,15],enabl:12,enclos:13,encompass:3,encount:8,end:[1,8,9,17],enforc:9,enforce_ngon_pe_loc:13,englob:13,enough:3,enrich:18,ensur:13,entir:[8,9],entiti:[8,9,12,13,15],env:10,environ:[3,5,6,7,10,14],eq1:1,equal:[1,8,13,15],equat:[1,13],equilibr:15,equival:[3,8,9],error:[3,12,14],essenti:9,etc:[7,8,15],even:[9,18],event:9,everi:[8,9,15,17],everyth:17,exactli:[1,13],exampl:[4,8,10,13,15,16,17,18],except:[1,9,13,17],excepthook:14,exchang:[1,8,13,17],exchange_field:13,exclud:[7,9,13,17],exclude_dict:17,exclus:9,execut:[1,5,8,14],exercis:9,exist:[3,8,13,15,17,18],expect:[4,10,13,15,17],expected_:4,experiment:[1,13],explain:[8,14],explicitli:[8,9,10,13],exploit:9,express:[9,15],extend:15,extens:[3,6,11],extent:9,exterior:1,extern:[1,5,7,13,15],extract:[6,10],extract_part_from_bc_nam:[10,13],extract_part_from_zsr:13,extract_tre:10,extract_tree_dist:10,extracted_bc:13,extracted_tre:13,extractor:13,f:[1,10,13],face:[4,6,8,13,15],face_cel:4,face_cent:13,face_renum_method:15,face_vtx:4,facecent:[1,13,15],facevtx:1,fact:8,factori:[10,12,13,16,17,18],factual:9,fail:[9,13],failur:9,fake:13,fals:[13,15,16],famili:[10,13],family_nam:13,family_t:13,familynam:[12,13],familyname_t:13,faster:1,featur:[10,13],fee:9,feel:3,few:[7,13,18],field:[8,10,13,15,18],fifti:9,figur:10,file:[5,7,8,9,10,14,18],file_print:[3,14],file_to_dist_tre:[1,10,13,15,16,17],filenam:[16,17],fill:[8,16],fill_size_tre:16,filter:[7,16,17],find:[1,13],find_closest_point:13,fine:8,finer:16,first:[1,6,8,9,12,13],firstsolut:17,fit:9,flat:[10,13],flowsolut:[13,15,17],flowsolution_t:[8,10,13,17],fluid:11,folder:[3,5],follow:[1,7,8,9,10,13,14,15,16,17],footnot:8,forc:14,form:8,formal:8,format:10,former:10,found:[7,10,13,16],foundat:9,fr:11,fraction:15,framework:11,free:9,friendli:10,from:[1,3,7,8,9,10,12,13,16,17,18],fsol:13,full:[8,13,14,15,16],full_onera_compat:13,fulli:[8,10],further:9,g:[1,5,7,8,14,17,18],gather:[1,8,13,16],gc:[12,13],gc_t:[8,13],gcc:7,gener:[5,6,9,11,12,14,16,18],generate_dist_block:[10,12,13,15,16],generate_dist_point:15,generate_dist_spher:15,generate_jns_vertex_list:13,geodes:15,geometr:[8,10,15,17],get:[1,5,7,8,15],get_all_zone_t:[13,15,17],get_child_from_label:[10,15],get_child_from_nam:13,get_nam:13,get_node_from_label:[13,15],get_node_from_nam:13,get_node_from_path:17,get_nodes_from_nam:13,get_rank:[3,13,15,16],get_siz:15,git:[5,7],git_config_submodul:[5,7],github:[7,11],gitlab:[6,7,10],give:8,given:[8,9,10,13,15],global:[1,3,8,14,17],globalnumb:[8,15,16],gnu:9,gnum:[13,15],go:10,goe:8,good:8,goodwil:9,govern:9,gradient:10,grai:10,graph:15,graph_part_tool:[13,15],green:8,grid:[13,15],gridconnect:[12,13],gridconnectivity1to1_t:13,gridconnectivity_0_to_1:8,gridconnectivity_t:13,gridconnectivityproperti:13,gridcoordin:[13,15],gridcoordinate_t:13,gridcoordinates_t:8,gridloc:[1,13,15],gridlocation_t:10,group:10,guarante:13,guid:16,h5py:16,ha:[7,8,9,12,13,16],hand:15,handl:3,happen:8,hard:4,have:[1,7,8,9,10,13,14,15,17],have_isolated_fac:13,hdf5:[7,8,16],hdf:[10,16],he:3,heavi:8,heavyweight:1,held:9,help:10,helper:7,helpful:11,here:[1,5,7,10],herebi:9,hereof:9,hesit:10,heterogen:[8,15],hex:1,hexa:[1,8,13],hexa_8:[4,15],high:[6,10,11,16,17],higher:[13,15],hilbert:[13,15],hold:[4,8,15],homogen:15,host:6,how:[5,8,9,14,16],howev:[7,8,9,15,17],hpc:15,hpp:3,html:7,http:[7,9,11],hybrid:15,hyperplan:15,i:[1,8,9,13,15],i_rank:[13,15],icosahedr:15,id:[1,8,13,16],ident:1,idw:13,idw_pow:13,ie:[13,15],ii:9,illustr:[8,10,16],imagin:8,impi:10,implement:[8,13,15,18],impli:[1,3,9],implicit:[8,10],imposs:9,inaccuraci:9,incident:9,includ:[1,3,8,9,10,13,15,17],include_dict:17,increas:13,incur:9,indemn:9,indemnifi:9,independ:[8,13,16],index:[4,8],indic:[8,13],indirect:9,indirectli:9,indistinctli:13,individu:9,induc:8,info:[3,7,14],inform:[1,8,9,12,14,15,16],informat:7,infra:7,infring:9,init:[5,7,13],initi:[9,13,15],injuri:9,inplac:[13,17,18],input:[1,10,13,15,18],insert:[1,16],insid:8,instal:[10,15,16],install_dir:7,instanc:[3,7,8],instanci:[3,13],instead:7,instruct:14,insur:10,int32:13,integ:[8,10,15],intel2120:10,intel:[7,10],intellectu:9,intend:9,interfac:15,interlac:13,intern:[1,8,13],interpolate_from_part_tre:13,interpret:3,interv:[4,8],introduc:8,introduct:[6,10],intuit:8,invers:[13,16],invert:1,investig:1,involv:[1,10,13],io:[1,6,10,11,13,15,17],irrelev:15,irrespect:13,is_same_tre:15,iso_field:13,iso_part_tre:13,iso_surfac:13,iso_v:13,isobarycent:13,isol:13,isosurf:13,isosurf_tre:13,isosurfac:[12,13],issu:[6,10],iter_all_zone_t:13,ith:8,its:[1,3,7,8,9,10,13,15],itself:[3,8,16],j:[7,8],jn:13,jn_path:13,jn_paths_for_dupl:13,job:10,join:[13,17],joint:[1,5],jth:8,judgment:9,judici:9,jurisdict:9,just:[1,7,8],k:1,keep:[1,8,13,15,16],kei:[8,13,17],kept:1,keyword:15,kind:[1,6,7,8,9,13,15,17,18],know:8,knowledg:8,known:[9,15,17],kwarg:[13,15],label:[1,13,17],languag:9,larg:[10,15],last:7,lastli:17,later:9,latest:10,latter:3,law:9,ld_library_path:[5,7],lead:15,least:15,left:[1,15],legaci:[8,10,13,16],legal:9,len:[13,15],length:[8,15],less:[8,15],lesser:9,let:[1,8,16],level:[5,6,8,10,11,13,15,16],lexicograph:[1,15],liabl:9,librari:[3,4,6,7,8,10,11,15,16],light:14,lightweight:8,like:[3,7,8,9,13,14],limit:[8,13],line:[7,10],link:[8,16],list:[13,15,16,17],lista:13,listb:13,listen:3,ln:8,ln_to_gn:8,load:[8,10,15,16,18],load_collective_size_tre:16,loc:13,loc_contain:13,loc_toler:13,local:[1,3,4,8,13],localize_point:13,locat:[7,8,9,10,13,15],locationandclosest:13,log:[1,2],logger:14,logging_conf_fil:[3,14],logo:9,look:[1,3,8,9,10,14],loss:9,lost:9,low:[8,11,16],lower:[1,13],lowest:13,m:[15,17],machin:7,made:[6,9,13],magic:17,mai:[7,8,9,16,17],maia:[1,5,7,10,12,13,14,15,16,17,18],maia_build_fold:7,maia_doctest_unit_test:5,maia_fold:[5,7],maia_poly_new_to_old:10,maia_poly_old_to_new:10,main:[6,16,18],maintain:[9,10],make:[7,8,9,13],malfunct:9,manag:[2,6,7,8,9,12,14,15,17],mandatori:13,mani:15,manner:[9,16],manual:[6,10],manuel:18,map:[8,10,13,17],mark:9,match:13,matcha:[10,13],matchb:[10,13],materi:[9,10],mathcal:1,mathtt:8,matrix:13,matter:9,max_coord:15,maximum:9,mean:[1,4,7,8,9,10,13,15,17],memori:[1,8,12,14],merchant:9,merg:[10,13],merge_all_zones_from_famili:13,merge_connected_zon:[10,13],merge_zon:[12,13],merge_zones_from_famili:13,merged_zon:13,mergedzon:13,mesh:[1,4,11,12,15,17],mesh_dir:[1,13,15],mesh_fil:10,messag:3,metadata:13,method:[3,8,9,13,15,17,18],mid:11,minim:[10,13,15],miss:[13,15,17],mix:[1,8,10,13],mlog:3,modif:9,modifi:[1,13,18],modul:[7,10,11,12,16,18],momentum:17,momentumi:17,momentumx:17,momentumz:17,more:[3,6,8,9,13,14,17],moreov:9,most:[3,6,8,10],move:13,move_field:13,mpart:15,mpi4pi:[1,7,10,13,15,16,17],mpi:[1,3,5,7,10,13,14,15,16,17],mpi_abort:14,mpi_comm:1,mpi_file_print:3,mpi_rank_0_stderr_print:[3,14],mpi_rank_0_stdout_print:[3,14],mpi_stderr_print:3,mpi_stdout_print:[3,14],mpicomm:[13,15,16,17],mpirun:5,mpl:9,msg:3,much:[1,8,10],multipl:[10,13],must:[7,8,9,10,13,15,17],my:3,my_app:3,my_fil:3,my_logg:3,my_print:3,my_them:3,mybas:8,mynod:8,myzon:8,n0:8,n1:10,n1xn2:10,n2:10,n:[1,8,13],n_:1,n_cell:13,n_cell_in_sect:1,n_cell_per_cach:15,n_face_of_cell_typ:1,n_face_per_pack:15,n_i:1,n_part:15,n_part_tot:15,n_proc:4,n_rank:[8,15],n_thing:4,n_vtx:[4,15],naca0012:13,name:[3,7,9,13,15,16],nan:13,natur:18,ncell:13,nearest:13,nearli:15,necessari:9,necessarili:8,need:[1,3,7,8,13,15,17],neglig:9,neighbour:13,net:7,never:8,new_child:13,new_dataarrai:13,new_flowsolut:13,new_nod:13,new_zonesubregion:13,next:[8,10,17],nface:[8,13,15],nface_n:[4,10,13,15],nface_to_p:13,nfaceel:13,nfacenod:13,ngon:[8,10,12,13,15],ngon_n:[10,13,15],ngonel:15,ngons_to_el:13,nodal:13,node:[1,8,10,12,13,15,16,17],non:[8,9,10,15],none:[3,13,15,17],norm:8,normal:1,notabl:[5,8],notat:11,note:[1,7,8,9,10,13,14],noth:9,notic:8,notifi:9,notwithstand:9,now:[1,3,8,10,13,16],np:[5,13],number:[1,4,5,9,13,15],numpi:13,ny:8,o:1,object:[3,8,13],oblig:9,observ:10,obtain:[9,15],occur:[1,12,13,14],odd:13,off:1,offer:9,often:[5,7,8,10,18],old:[10,13],onc:[1,7,10,13],one:[1,7,8,9,10,13,16,17],onera:[6,7,11,13],onera_spack_repo:7,ones:[1,5,7,8,13,15],ongo:9,onli:[1,4,8,9,13,15,16,17,18],only_uniform:15,open:[4,6,8],oper:[1,10,13,15,16,17,18],opposit:[10,13],opposite_jn:10,option:[8,9,13,17],order:[1,5,6,8,9,10,13,15],ordinari:9,org:9,organ:5,orient:[3,15],origin:[1,8,9,13,15],os:17,other:[5,8,9,10,13,15],otherwis:[9,13,15,16],our:8,out:[7,10],out_fs_nam:13,outlin:10,output:[3,13,14,15,16],output_path:13,outstand:9,over:[1,6,8,13,15],overrid:[7,14],overview:6,own:[7,8,9,10,14,18],owner:15,ownership:9,p0:8,p1:8,p2:8,packag:[7,10],page:[6,12,14],pair:13,paradigm:[7,8,15],paradigma:13,parallel:[6,10,13,15,16,18],parallel_sort:1,paramet:[13,15,16,17],parameter:5,parametr:13,parent:[1,4,10,13,15],parentel:[1,4,13],parentelementsposit:1,parentposit:1,parmeti:[13,15],part:[6,8,9,10,12,13,15,18],part_interface_loc:15,part_tre:[10,13,15,16,17],part_tree_iso:13,part_tree_src:13,part_tree_tgt:13,part_tree_to_dist_tree_al:[10,17],part_tree_to_dist_tree_only_label:17,part_tree_to_fil:16,part_zon:[13,17],part_zones_to_dist_zone_al:17,part_zones_to_dist_zone_onli:17,parti:[7,9],partial:[1,8],particular:[1,3,7,9],partion:13,partit:[1,6,10,12,17,18],partition_dist_tre:[10,13,15,16,17],patch:17,patent:9,path:[7,10,13,15,16,17],pattern:1,pe:15,pe_to_nfac:[10,13],peak:1,penalti:8,penta_6:15,per:[8,15],percent:9,perfect:[1,6],perform:[9,10,13,15,16],period:[12,13],permit:9,person:9,physic:15,pi:13,pick:17,pictur:10,piec:8,pip3:7,place:[1,9,13],plane:[10,13],plane_eq:13,plane_slic:[10,13],planeslic:10,point:[5,8,12,13,15],point_cloud:[10,13],point_list:13,pointlist:[1,8,13],pointlistdonor:13,pointrang:8,pointrange_t:10,poli:[13,15,16],polici:13,poly_new_to_old:13,poly_old_to_new:13,polyedr:13,polygon:13,polyhedr:[10,13],polyhedra:15,poor:15,popul:7,portabl:11,portion:9,posit:[1,8,15],possibl:[8,9,10,15,17],post:[10,11],power:[9,13],practic:[1,7,10],pre:[1,10,11,15],prefer:[9,10],prefix:3,present:[7,8,13],preserv:[8,10,13],preserve_orient:[13,15],pressur:17,prevent:[9,12],previou:[1,8],previous:1,princip:9,print:3,printer:14,prior:9,prism:[1,13],privileg:16,proc:[4,8,13,15],process:[1,4,5,7,8,9,10,11,13,14,15,16],produc:[8,13,15,16,18],product:10,profit:9,program:3,progress:11,prohibit:9,project:13,project_build_dir:5,project_src_dir:5,project_util:7,propag:13,properli:8,properti:[1,8,9],prove:9,provid:[0,3,6,7,8,9,10,11,13,14,15,16,17,18],provis:9,provision:9,pt:[10,13,15,17],ptscotch:[13,15],publish:9,pure:3,purpl:8,purpos:[9,14,16],put:[3,8,9,10],px:8,pybind11:7,pycgn:10,pyra:[1,13],pyra_5:[13,15],pytest:[5,7],pytest_check:7,pytest_parallel:[7,12],python3:7,python:[3,4,6,7,8,10,11,14,16],pythonpath:[5,7],pytre:[10,13,15,16,17],quad:[1,8,13],quad_4:[8,13,15],quad_4_interior:1,qualiti:9,quantiti:8,quarter_crown_square_8:17,quick:6,quickselect:1,quit:7,r:13,radiu:15,rais:14,rand:13,random:[13,15],rang:[7,13,15],rank:[1,8,14,15,16,17],rank_id:15,rather:3,raw:15,read:[3,10,13,16],read_tre:[15,16],readi:[7,10],rearang:13,rearrange_element_sect:13,reason:[8,9],receipt:9,receiv:[1,3,9,10,13,15],recip:7,recipi:9,recommend:10,reconstitut:13,reconstruct:[8,12],record:13,recov:12,recover_dist_tre:[10,15],red:8,redistribut:13,redistribute_tre:13,reduc:[12,13],reduct:13,redund:8,refer:[3,8,9,13,15],referencest:12,referencestate_t:13,reform:9,regener:15,regular:[1,8,13],reinstat:9,rel:13,relat:[8,9,13,16,17],relax:8,releas:10,relev:[9,13,15,17],reli:[10,11],remain:13,remedi:9,remot:1,remov:[7,9,13,16],remove_p:13,removenfac:13,removep:13,renam:[9,13],renumb:[8,15],repair:9,repart:13,repartit:8,replac:[1,13],replic:8,repo:7,report:[10,13,15],repositori:[5,6,7],repres:[8,9,10],reproduc:9,request:[13,15,16],requir:[1,7,8,9,13,17],resel:9,reshap:13,resp:[13,17],respect:[3,8,9,10],restrict:[9,13],result:[1,8,9,13,18],retriev:[6,11,13],revers:15,right:[1,9,15],risk:9,rk:3,rm_nodes_from_nam:[13,16],robust:12,room:10,root:13,rotat:13,rotation_angl:13,rotation_cent:13,round:8,royalti:9,ruamel:7,rule:17,run:[5,10],s:[6,7,8,9,10,13,15,16,17],s_twoblock:[10,13,15],said:8,sale:9,same:[1,7,8,10,13,15],sampl:[10,16],sample_mesh_dir:17,sator:10,save:[10,18],scalar:15,scale:1,scatter:1,scenario:8,scientif:11,scratchm:10,script:[3,10],sdtout:14,search:13,second:[8,13],secondsolut:17,section:[0,8,9,10,13,17],see:[1,7,8,9,10,13,14,15,16],seen:8,select:[3,12,13],self:[3,10],sell:9,semi:[4,8],send:[1,10],sens:8,separ:[9,16],seq:13,sequenc:13,sequenti:[1,10,16],servic:[9,11],set:[3,10,11,13,14,15],set_nam:10,setup:10,sever:[3,7,8,10],sh:[5,7,10],shall:9,shape:[8,10,13,16],share:[8,9,12,13],shift:[1,13],shortcut:13,should:[1,3,7,9,10,13,15,16],show:16,sid:[10,13,16],side1:13,side2:13,side:13,similar:[1,13,17],similarli:[10,13],simpl:10,simpli:[8,15],sin:10,sinc:[1,8,10],singl:[8,10,13,14,15,16],single_fil:16,singular:4,size:[1,4,8,10,13,15,16],size_tre:16,skeleton:8,skill:9,slice:[10,13],slice_tre:[10,13],small:[15,18],snake_cas:4,so:[3,4,7,8,9],socl:10,softwar:[6,9,11],solut:13,solver:[8,18],some:[0,6,7,8,9,10,12,13,14,16,18],someth:[1,8],sometim:8,sonic:10,sort:[1,8],sort_by_rank:1,sort_int_ext:15,sourc:[5,6,10,13,17,18],spack_root:7,spacki:7,span:8,spane:8,special:[9,13],specialsolut:17,specif:[9,15],specifi:[13,17],speedup:1,sphere:[13,15],sphere_eq:13,spheric:[12,13,15],spherical_slic:13,sphinx:7,spiro:10,split:[1,8,10,12,15],splitabl:8,spot:1,squar:8,src_dir:7,src_sol:13,src_tree:13,stable_sort:[1,13],stage:1,standalon:10,standard:[1,6,8,10,11,13,15],start:[3,6,8,13,17],start_rang:13,stat:[3,14],statutori:9,std:7,std_e:[1,3,5,7],std_e_unit_test:5,stderr:[3,14],stderr_print:3,stdout:3,stdout_print:3,step:[1,7,16],steward:9,still:[3,13],stoppag:9,storag:11,store:[1,8,13,16],str:[13,15,16,17],strategi:13,strict:15,stride:8,string:3,structur:[4,8,10,12,13,15,17,18],sub:[8,14],subfil:16,subject:9,sublicens:9,submesh:[8,13],submodul:5,subregion:[8,15],subset:[8,13],subset_loc:13,subset_merg:13,substanc:9,successfulli:13,suffici:9,suffix:[4,13],suit:8,sum:[1,8,15],sum_:1,summar:14,support:[9,13,15,16,17],suppos:[13,17],sure:7,surfac:[10,12,13,15],surviv:9,sy:14,system:[7,11],sz:4,t:[7,13],t_:1,t_p:1,tabl:14,take:[1,8,13,17],target:[13,17],tata:17,tell:7,term:8,termin:14,test:[3,4,10],test_util:[1,13,15,17],tet:[1,13],tetra:1,tetra_4:[10,15],text:9,textrm:1,tgt_sol:13,tgt_tree:13,tgt_zone:13,than:[1,3,8,9,13],thank:[1,8,10,13],thei:[1,3,7,8,9,10,13,16,17,18],them:[1,3,7,8,13],theme:3,theoret:1,theori:9,therefor:8,thereof:9,theses:10,thi:[0,1,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18],thing:4,third:[7,9],those:[1,8,9,13,15,17],thought:13,three:[1,15,18],through:[8,13,14,15,18],thu:[8,18],tild:13,time:[1,3,8,9,14],tl:5,tmp:7,tmp_user:10,tol:13,toler:[10,13],too:[8,15],tool:[10,15,18],topolog:[10,15],tort:9,total:[8,15],total_s:8,track:[6,8],trade:1,trademark:9,transfer:[8,9,10,12,13,15,18],transfer_dataset:13,transform:[6,10],transform_affin:13,translat:13,transmit:15,transpar:7,treat:8,treatement:13,tree:[1,6,10,12,13,15,18],tree_:10,tree_u:10,tri:[1,13],tri_3:[13,15],tri_3_interior:1,tune:13,tupl:13,turbulentdist:13,tutuz:17,twice:[1,3],two:[1,4,8,13,16,17,18],type:[1,3,8,13,15],typic:[3,7,8,18],u:[13,15],u_atb_45:[10,13],u_naca0012_multizon:13,u_twoblock:10,uelt_m6w:[1,13],unabl:10,uncatch:14,unchang:13,under:[1,6,9,10,12,13],underli:10,understand:9,undesir:14,unenforc:9,unfil:8,unfortun:4,uniform:[13,15],union:8,uniqu:[1,13,16],unit:4,unless:9,unloc:13,unmatch:13,unmodifi:9,unpartit:8,unsign:13,unstructur:[1,8,10,13,15,18],unstuctur:15,until:[3,8,9,13],unwant:16,up:[1,3,10,13],updat:[5,7,13,18],us:[1,3,4,5,6,7,8,10,12,13,14,15,16,17,18],usag:[10,14],user:[3,6,7,9,10,14,15],usr:10,usual:4,util:[1,3,7,10,13,15,17],v3:7,v5:10,v:[9,13],valid:13,validli:9,valu:[8,13,15],vari:1,variabl:[3,4,8,14],variant:1,variou:[6,13],vector:[13,15],vectori:13,verbos:5,veri:1,versa:3,version:[7,8,10,12],vertex:[4,8,10,13,15],vertic:[1,4,8,13,15],vice:3,view:[1,7,8],virtual:8,vol_rank:13,volum:[10,12,13],vtx:[4,13],vtx_renum_method:15,vtx_sol:13,wa:[1,8,9,15],wai:[7,8,13,14,16],wall:[10,13,18],walldist:[10,13],want:[3,5,6,7,8,9,10,16],warn:[3,13,14],we:[1,3,4,5,7,8,10,13,16,17],weight:[13,15],well:[8,15,16],were:[1,8,13],what:[3,8,12,16],when:[3,4,8,12,14,15,17],where:[1,3,7,8,9,13,15],whether:9,which:[1,5,8,9,10,11,13,14,15,16,17,18],white:10,who:[3,9,13,15],whose:[8,13,18],why:10,wide:9,wildcard:[12,13,17],within:[8,9,10,13,15,16],without:[7,9,15],work:[7,10],workflow:[2,10,16,18],world:[9,10],wors:1,worst:8,worth:3,would:[1,3,8,9],writ:8,write:11,write_tre:16,written:[4,8,16],x:[8,10,13],x_0:13,y:[8,13],y_0:13,yaml:[1,7,13,15,17],yet:[8,13,15],ym:16,you:[6,7,8,9,10,18],your:[7,9,10,14,16,18],z:13,z_0:13,zero:[13,15],zm:16,zone0:8,zone1:8,zone:[1,8,13,15,16],zone_path:[12,13],zone_t:[8,13,15,17],zone_to_part:15,zone_typ:15,zonebc:[15,17],zonegridconnect:[10,15],zonenam:13,zonesubregion:[13,15,17],zonesubregion_t:[8,13,17],zsr_name:13},titles:["Algorithms description","elements_to_ngons","Developer Manual","Log management","Conventions","Development workflow","Welcome to Maia!","Installation","Introduction","License","Quick start","Related projects","Release notes","Algo module","Configuration","Factory module","File management","Transfer module","User Manual"],titleterms:{"0":[9,12],"1":[9,12],"10":9,"2":9,"2023":12,"3":9,"4":9,"5":9,"6":9,"7":9,"8":9,"9":9,"cassiop\u00e9":11,"new":[9,12],"public":9,A:9,With:9,addit:9,advanc:12,algo:13,algorithm:[0,1,13],all:1,altern:1,applic:[8,9],argument:1,avail:3,b:9,build:7,calcul:13,cellfac:1,cgn:[8,11],code:9,complex:1,compli:9,comput:1,concept:8,condit:9,configur:[3,14],connect:13,convent:[4,8],convers:13,core:8,creat:3,data:[8,13],date:9,definit:9,depend:7,descript:[0,1],design:1,dev:12,develop:[2,5,7],disclaim:9,distribut:[8,9,13,16],divid:8,document:6,due:9,effect:9,elements_to_ngon:1,environn:10,exampl:1,except:14,execut:9,exhibit:9,explan:1,extract:13,face:1,factori:15,fair:9,featur:12,field:17,file:[3,16],fix:12,folder:7,form:9,from:15,gener:[1,13,15],geometr:13,geometri:13,grant:9,handl:14,highlight:10,improv:12,inabl:9,incompat:9,instal:7,interfac:13,interior:1,interpol:13,introduct:8,io:16,larger:9,launch:5,level:17,liabil:9,licens:9,limit:9,litig:9,log:[3,14],logger:3,mai:12,maia:[3,6,8],manag:[3,13,16],manual:[2,7,18],march:12,mesh:[8,10,13],miscellan:9,modifi:9,modul:[5,13,15,17],mozilla:9,mpi:8,name:[4,8],nface:1,ngon:1,note:12,notic:9,number:8,option:[7,15],other:[4,7],output:1,overview:8,own:3,paradigm:11,parallel:[1,8],partit:[8,13,15,16],prefer:7,printer:3,procedur:7,project:11,quick:10,raw:16,recov:15,regul:9,relat:11,releas:12,reorder:15,repartit:15,represent:9,resourc:10,respons:9,scope:9,secondari:9,section:1,sequenti:13,simplifi:1,sourc:[7,9],spack:7,specif:[3,8],start:10,statut:9,sub:5,submodul:7,subsequ:9,summari:6,support:10,term:9,termin:9,test:5,through:7,tool:13,transfer:17,transform:[1,13],tree:[8,16,17],troubleshout:10,us:9,user:[12,18],v1:12,version:9,warranti:9,welcom:6,work:9,workflow:[5,7],write:16,your:3,zone:17}}) \ No newline at end of file diff --git a/docs/1.1/user_manual/algo.html b/docs/1.1/user_manual/algo.html new file mode 100644 index 00000000..fae422b6 --- /dev/null +++ b/docs/1.1/user_manual/algo.html @@ -0,0 +1,1526 @@ + + + + + + + + + + Algo module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Algo module

+

The maia.algo module provides various algorithms to be applied to one of the +two kind of trees defined by Maia:

+
    +
  • maia.algo.dist module contains some operations applying on distributed trees

  • +
  • maia.algo.part module contains some operations applying on partitioned trees

  • +
+

In addition, some algorithms can be applied indistinctly to distributed or partitioned trees. +These algorithms are accessible through the maia.algo module.

+

The maia.algo.seq module contains a few sequential utility algorithms.

+
+

Distributed algorithms

+

The following algorithms applies on maia distributed trees.

+
+

Connectivities conversions

+
+
+convert_s_to_u(disttree_s, connectivity, comm, subset_loc={})
+

Performs the destructuration of the input dist_tree.

+

This function copies the GridCoordinate_t and (full) FlowSolution_t nodes, +generate a NGon based connectivity and create a PointList for the following +subset nodes: +BC_t, BCDataSet_t and GridConnectivity1to1_t. +In addition, a PointListDonor node is generated for GridConnectivity_t nodes.

+

Metadata nodes (“FamilyName_t”, “ReferenceState_t”, …) at zone and base level +are also reported on the unstructured tree.

+
+

Note

+

Exists also as convert_s_to_ngon() with connectivity set to +NGON_n and subset_loc set to FaceCenter.

+
+
+
Parameters
+
    +
  • disttree_s (CGNSTree) – Structured tree

  • +
  • connectivity (str) – Type of elements used to describe the connectivity. +Admissible values are "NGON_n" and "HEXA" (not yet implemented).

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • subset_loc (dict, optional) – Expected output GridLocation for the following subset nodes: BC_t, GC_t. +For each label, output location can be a single location value, a list +of locations or None to preserve the input value. Defaults to None.

  • +
+
+
Returns
+

CGNSTree – Unstructured disttree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree_s = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+dist_tree_u = maia.algo.dist.convert_s_to_u(dist_tree_s, 'NGON_n', MPI.COMM_WORLD)
+for zone in maia.pytree.get_all_Zone_t(dist_tree_u):
+  assert maia.pytree.Zone.Type(zone) == "Unstructured"
+
+
+
+ +
+
+convert_elements_to_ngon(dist_tree, comm, stable_sort=False)
+

Transform an element based connectivity into a polyedric (NGon based) +connectivity.

+

Tree is modified in place : standard element are removed from the zones +and the PointList are updated. If stable_sort is True, face based PointList +keep their original values.

+

Requirement : the Element_t nodes appearing in the distributed zones +must be ordered according to their dimension (either increasing or +decreasing).

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by standard elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • stable_sort (bool, optional) – If True, 2D elements described in the +elements section keep their original id. Defaults to False.

  • +
+
+
+

Note that stable_sort is an experimental feature that brings the additional +constraints:

+
+
    +
  • 2D meshes are not supported;

  • +
  • 2D sections must have lower ElementRange than 3D sections.

  • +
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+ngons_to_elements(t, comm)
+

Transform a polyedric (NGon) based connectivity into a standard nodal +connectivity.

+

Tree is modified in place : Polyedric element are removed from the zones +and Pointlist (under the BC_t nodes) are updated.

+

Requirement : polygonal elements are supposed to describe only standard +elements (ie tris, quads, tets, pyras, prisms and hexas)

+

WARNING: this function has not been parallelized yet

+
+
Parameters
+
    +
  • disttree (CGNSTree) – Tree with connectivity described by NGons

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +
+
+convert_elements_to_mixed(dist_tree, comm)
+

Transform an element based connectivity into a mixed connectivity.

+

Tree is modified in place : standard elements are removed from the zones. +Note that the original ordering of elements is preserved.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by standard elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+convert_mixed_to_elements(dist_tree, comm)
+

Transform a mixed connectivity into an element based connectivity.

+

Tree is modified in place : mixed elements are removed from the zones +and the PointList are updated.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by mixed elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD)
+maia.algo.dist.convert_mixed_to_elements(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+rearrange_element_sections(dist_tree, comm)
+

Rearanges Elements_t sections such that for each zone, +sections are ordered in ascending dimensions order +and there is only one section by ElementType. +Sections are renamed based on their ElementType.

+

The tree is modified in place. +The Elements_t nodes are guaranteed to be ordered by ascending ElementRange.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with an element-based connectivity

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_block(11, 'PYRA_5', MPI.COMM_WORLD)
+pyras = PT.get_node_from_name(dist_tree, 'PYRA_5.0')
+assert PT.Element.Range(pyras)[0] == 1 #Until now 3D elements are first
+
+maia.algo.dist.rearrange_element_sections(dist_tree, MPI.COMM_WORLD)
+tris = PT.get_node_from_name(dist_tree, 'TRI_3') #Now 2D elements are first
+assert PT.Element.Range(tris)[0] == 1
+
+
+
+ +
+
+generate_jns_vertex_list(dist_tree, comm, have_isolated_faces=False)
+

For each 1to1 FaceCenter matching join found in the distributed tree, +create a corresponding 1to1 Vertex matching join.

+

Input tree is modified inplace: Vertex GridConnectivity_t nodes +are stored under distinct containers named from the original ones, suffixed +with #Vtx. Similarly, vertex GC nodes uses the original name suffixed +with #Vtx.

+

Only unstructured-NGon based meshes are supported.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • have_isolated_faces (bool, optional) – Indicate if original joins includes +faces who does not share any edge with other external (join) faces. +If False, disable the special treatement needed by such faces (better performances, +but will fail if isolated faces were actually present). +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree_s = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+dist_tree = maia.algo.dist.convert_s_to_ngon(dist_tree_s, MPI.COMM_WORLD)
+
+maia.algo.dist.generate_jns_vertex_list(dist_tree, MPI.COMM_WORLD)
+assert len(maia.pytree.get_nodes_from_name(dist_tree, 'match*#Vtx')) == 2
+
+
+
+ +
+
+

Geometry transformations

+
+
+duplicate_from_rotation_jns_to_360(dist_tree, zone_paths, jn_paths_for_dupl, comm, conformize=False, apply_to_fields=False)
+

Reconstitute a circular mesh from an angular section of the geometry.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input distributed tree

  • +
  • zone_paths (list of str) – List of pathes (BaseName/ZoneName) of the connected zones to duplicate

  • +
  • jn_paths_for_dupl (pair of list of str) – (listA, listB) where listA (resp. list B) stores all the +pathes of the GridConnectivity nodes defining the first (resp. second) side of a periodic match.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • conformize (bool, optional) – If true, ensure that the generated interface vertices have exactly same +coordinates (see conformize_jn_pair()). Defaults to False.

  • +
  • apply_to_fields (bool, optional) – See maia.algo.transform_affine(). Defaults to False.

  • +
+
+
+
+ +
+
+merge_zones(tree, zone_paths, comm, output_path=None, subset_merge='name', concatenate_jns=True)
+

Merge the given zones into a single one.

+

Input tree is modified inplace : original zones will be removed from the tree and replaced +by the merged zone. Merged zone is added with name MergedZone under the first involved Base +except if output_path is not None : in this case, the provided path defines the base and zone name +of the merged block.

+

Subsets of the merged block can be reduced thanks to subset_merge parameter:

+
    +
  • None : no reduction occurs : all subset of all original zones remains on merged zone, with a +numbering suffix.

  • +
  • 'name' : Subset having the same name on the original zones (within a same label) produces +and unique subset on the output merged zone.

  • +
+

Only unstructured-NGon trees are supported, and interfaces between the zones +to merge must have a FaceCenter location.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • zone_paths (list of str) – List of path (BaseName/ZoneName) of the zones to merge. +Wildcard * are allowed in BaseName and/or ZoneName.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • output_path (str, optional) – Path of the output merged block. Defaults to None.

  • +
  • subset_merge (str, optional) – Merging strategy for the subsets. Defaults to ‘name’.

  • +
  • concatenate_jns (bool, optional) – if True, reduce the multiple 1to1 matching joins related +to the merged_zone to a single one. Defaults to True.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 3
+
+maia.algo.dist.merge_zones(dist_tree, ["BaseA/blk1", "BaseB/blk2"], MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 2
+
+
+
+ +
+
+merge_zones_from_family(tree, family_name, comm, **kwargs)
+

Merge the zones belonging to the given family into a single one.

+

See merge_zones() for full documentation.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • family_name (str) – Name of the family (read from FamilyName_t node) +used to select the zones.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • kwargs – any argument of merge_zones(), excepted output_path

  • +
+
+
+
+

See also

+

Function merge_all_zones_from_families(tree, comm, **kwargs) does +this operation for all the Family_t nodes of the input tree.

+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+
+# FamilyName are not included in the mesh
+for zone in PT.get_all_Zone_t(dist_tree):
+  PT.new_child(zone, 'FamilyName', 'FamilyName_t', 'Naca0012')  
+
+maia.algo.dist.merge_zones_from_family(dist_tree, 'Naca0012', MPI.COMM_WORLD)
+
+zones = PT.get_all_Zone_t(dist_tree)
+assert len(zones) == 1 and PT.get_name(zones[0]) == 'naca0012'
+
+
+
+ +
+
+merge_connected_zones(tree, comm, **kwargs)
+

Detect all the zones connected through 1to1 matching jns and merge them.

+

See merge_zones() for full documentation.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • kwargs – any argument of merge_zones(), excepted output_path

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+
+maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 1
+
+
+
+ +
+
+conformize_jn_pair(dist_tree, jn_paths, comm)
+

Ensure that the vertices belonging to the two sides of a 1to1 GridConnectivity +have the same coordinates.

+

Matching join with Vertex or FaceCenter location are admitted. Coordinates +of vertices are made equal by computing the arithmetic mean of the two +values.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input tree

  • +
  • jn_pathes (list of str) – Pathes of the two matching GridConnectivity_t +nodes. Pathes must start from the root of the tree.

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +
+
+

Interface tools

+
+
+connect_1to1_families(dist_tree, families, comm, periodic=None, **kwargs)
+

Find the matching faces between cgns nodes belonging to the two provided families.

+

For each one of the two families, all the BC_t or GridConnectivity_t nodes related to the family +through a FamilyName/AdditionalFamilyName node will be included in the pairing process. +These subset must have a Vertex or FaceCenter GridLocation.

+

If the interface is periodic, the transformation from the first to the second family +entities must be specified using the periodic argument; a dictionnary with keys +'translation', 'rotation_center' and/or 'rotation_angle' is expected. +Each key maps to a 3-sized numpy array, with missing keys defaulting zero vector.

+

Input tree is modified inplace : relevant GridConnectivity_t with PointList and PointListDonor +data are created. +If all the original elements are successfully paired, the original nodes are removed. Otherwise, +unmatched faces remains in their original node which is suffixed by ‘_unmatched’.

+

This function allows the additional optional parameters:

+
    +
  • location (default = ‘FaceCenter’) – Controls the output GridLocation of +the created interfaces. ‘FaceCenter’ or ‘Vertex’ are admitted.

  • +
  • tol (default = 1e-2) – Geometric tolerance used to pair two points. Note that for each vertex, this +tolerance is relative to the minimal distance to its neighbouring vertices.

  • +
+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input distributed tree. Only U-NGon connectivities are managed.

  • +
  • families (tuple of str) – Name of the two families to connect.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • periodic (dic, optional) – Transformation from first to second family if the interface is periodic. +None otherwise. Defaults to None.

  • +
  • **kwargs – Additional options

  • +
+
+
+

Example

+
from mpi4py import MPI
+from numpy  import array, pi
+import maia
+import maia.pytree as PT
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+
+# Remove data that should be created
+PT.rm_nodes_from_name(dist_tree, 'PointListDonor')
+PT.rm_nodes_from_name(dist_tree, 'GridConnectivityProperty')
+
+# Create FamilyName on interface nodes
+PT.new_node('FamilyName', 'FamilyName_t', 'Side1', 
+        parent=PT.get_node_from_name(dist_tree, 'matchA'))
+PT.new_node('FamilyName', 'FamilyName_t', 'Side2', 
+        parent=PT.get_node_from_name(dist_tree, 'matchB'))
+
+maia.algo.dist.connect_1to1_families(dist_tree, ('Side1', 'Side2'), MPI.COMM_WORLD,
+        periodic={'rotation_angle' : array([-2*pi/45.,0.,0.])})
+
+assert len(PT.get_nodes_from_name(dist_tree, 'PointListDonor')) == 2
+
+
+
+ +
+
+

Data management

+
+
+redistribute_tree(dist_tree, comm, policy='uniform')
+

Redistribute the data of the input tree according to the choosen distribution policy.

+

Supported policies are:

+
    +
  • uniform : each data array is equally reparted over all the processes

  • +
  • gather.N : all the data are moved on process N

  • +
  • gather : shortcut for gather.0

  • +
+

In both case, tree structure remains unchanged on all the processes +(the tree is still a valid distributed tree). +Input is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • policy (str) – distribution policy (see above)

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree_ini = maia.factory.generate_dist_block(21, 'Poly', MPI.COMM_WORLD)
+dist_tree_gathered = maia.algo.dist.redistribute_tree(dist_tree_ini, \
+    MPI.COMM_WORLD, policy='gather.0')
+
+
+
+ +
+
+
+

Partitioned algorithms

+

The following algorithms applies on maia partitioned trees.

+
+

Geometric calculations

+
+
+compute_cell_center(zone)
+

Compute the cell centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node. +Centers are computed using a basic average over the vertices of the cells.

+
+
Parameters
+

zone (CGNSTree) – Partitionned CGNS Zone

+
+
Returns
+

cell_center (array) – Flat (interlaced) numpy array of cell centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  cell_center = maia.algo.part.compute_cell_center(zone)
+
+
+
+ +
+
+compute_face_center(zone)
+

Compute the face centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node.

+

Centers are computed using a basic average over the vertices of the faces.

+
+

Note

+

If zone is described with standard elements, centers will be computed for elements +explicitly defined in cgns tree.

+
+
+
Parameters
+

zone (CGNSTree) – Partitionned 2D or 3D U CGNS Zone

+
+
Returns
+

face_center (array) – Flat (interlaced) numpy array of face centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  face_center = maia.algo.part.compute_face_center(zone)
+
+
+
+ +
+
+compute_edge_center(zone)
+

Compute the edge centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node, and a unstructured standard elements connectivity.

+
+

Note

+

If zone is described with standard elements, centers will be computed for elements +explicitly defined in cgns tree.

+
+
+
Parameters
+

zone (CGNSTree) – Partitionned 2D or 3D U-elts CGNS Zone

+
+
Returns
+

face_center (array) – Flat (interlaced) numpy array of edge centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, "QUAD_4",  MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  edge_center = maia.algo.part.geometry.compute_edge_center(zone)
+
+
+
+ +
+
+compute_wall_distance(part_tree, comm, *, method='cloud', families=[], point_cloud='CellCenter', out_fs_name='WallDistance')
+

Compute wall distances and add it in tree.

+

For each volumic point, compute the distance to the nearest face belonging to a BC of kind wall. +Computation can be done using “cloud” or “propagation” method.

+
+

Note

+

Propagation method requires ParaDiGMa access and is only available for unstructured cell centered +NGon connectivities grids. In addition, partitions must have been created from a single initial domain +with this method.

+
+
+

Important

+

Distance are computed to the BCs belonging to one of the families specified in families list. +If list is empty, we try to auto detect wall-like families. +In both case, families are (for now) the only way to select BCs to include in wall distance computation. +BCs having no FamilyName_t node are not considered.

+
+

Tree is modified inplace: computed distance are added in a FlowSolution container whose +name can be specified with out_fs_name parameter.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Input partitionned tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • method ({'cloud', 'propagation'}, optional) – Choice of method. Defaults to “cloud”.

  • +
  • point_cloud (str, optional) – Points to project on the surface. Can either be one of +“CellCenter” or “Vertex” (coordinates are retrieved from the mesh) or the name of a FlowSolution +node in which coordinates are stored. Defaults to CellCenter.

  • +
  • families (list of str) – List of families to consider as wall faces.

  • +
  • out_fs_name (str, optional) – Name of the output FlowSolution_t node storing wall distance data.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, families=["WALL"])
+assert maia.pytree.get_node_from_name(part_tree, "WallDistance") is not None
+
+
+
+ +
+
+localize_points(src_tree, tgt_tree, location, comm, **options)
+

Localize points between two partitioned trees.

+

For all the points of the target tree matching the given location, +search the cell of the source tree in which it is enclosed. +The result, i.e. the gnum & domain number of the source cell (or -1 if the point is not localized), +are stored in a DiscreteData_t container called “Localization” on the target zones.

+

Source tree must be unstructured and have a NGon connectivity.

+

Localization can be parametred thought the options kwargs:

+
    +
  • loc_tolerance (default = 1E-6) – Geometric tolerance for the method.

  • +
+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned. Only U-NGon connectivities are managed.

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned. Structured or U-NGon connectivities are managed.

  • +
  • location ({'CellCenter', 'Vertex'}) – Target points to localize

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • **options – Additional options related to location strategy

  • +
+
+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt):
+  maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3])
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+
+maia.algo.part.localize_points(part_tree_src, part_tree_tgt, 'CellCenter', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt):
+  loc_container = PT.get_child_from_name(tgt_zone, 'Localization')
+  assert PT.Subset.GridLocation(loc_container) == 'CellCenter'
+
+
+
+ +
+
+find_closest_points(src_tree, tgt_tree, location, comm)
+

Find the closest points between two partitioned trees.

+

For all points of the target tree matching the given location, +search the closest point of same location in the source tree. +The result, i.e. the gnum & domain number of the source point, are stored in a DiscreteData_t +container called “ClosestPoint” on the target zones. +The ids of source points refers to cells or vertices depending on the chosen location.

+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned

  • +
  • location ({'CellCenter', 'Vertex'}) – Entity to use to compute closest points

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt):
+  maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3])
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+
+maia.algo.part.find_closest_points(part_tree_src, part_tree_tgt, 'Vertex', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt):
+  loc_container = PT.get_child_from_name(tgt_zone, 'ClosestPoint')
+  assert PT.Subset.GridLocation(loc_container) == 'Vertex'
+
+
+
+ +
+
+

Mesh extractions

+
+
+iso_surface(part_tree, iso_field, comm, iso_val=0.0, containers_name=[], **options)
+

Create an isosurface from the provided field and value on the input partitioned tree.

+

Isosurface is returned as an independant (2d) partitioned CGNSTree.

+
+

Important

+
    +
  • Input tree must be unstructured and have a ngon connectivity.

  • +
  • Input tree must have been partitioned with preserve_orientation=True partitioning option.

  • +
  • Input field for isosurface computation must be located at vertices.

  • +
  • This function requires ParaDiGMa access.

  • +
+
+
+

Note

+
    +
  • Once created, additional fields can be exchanged from volumic tree to isosurface tree using +_exchange_field(part_tree, iso_part_tree, containers_name, comm).

  • +
  • If elt_type is set to ‘TRI_3’, boundaries from volumic mesh are extracted as edges on +the isosurface (GridConnectivity_t nodes become BC_t nodes) and FaceCenter fields are allowed to be exchanged.

  • +
+
+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree on which isosurf is computed. Only U-NGon +connectivities are managed.

  • +
  • iso_field (str) – Path (starting at Zone_t level) of the field to use to compute isosurface.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • iso_val (float, optional) – Value to use to compute isosurface. Defaults to 0.

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output isosurface tree.

  • +
  • **options – Options related to plane extraction.

  • +
+
+
Returns
+

isosurf_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Extraction can be controled thought the optional kwargs:

+
+
    +
  • elt_type (str) – Controls the shape of elements used to describe +the isosurface. Admissible values are TRI_3, QUAD_4, NGON_n. Defaults to TRI_3.

  • +
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, families=["WALL"], point_cloud='Vertex')
+
+part_tree_iso = maia.algo.part.iso_surface(part_tree, "WallDistance/TurbulentDistance", iso_val=0.25,\
+    containers_name=['WallDistance'], comm=MPI.COMM_WORLD)
+
+assert maia.pytree.get_node_from_name(part_tree_iso, "WallDistance") is not None
+
+
+
+ +
+
+plane_slice(part_tree, plane_eq, comm, containers_name=[], **options)
+

Create a slice from the provided plane equation \(ax + by + cz - d = 0\) +on the input partitioned tree.

+

Slice is returned as an independant (2d) partitioned CGNSTree. See iso_surface() +for use restrictions and additional advices.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree to slice. Only U-NGon connectivities are managed.

  • +
  • sphere_eq (list of float) – List of 4 floats \([a,b,c,d]\) defining the plane equation.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output slice tree.

  • +
  • **options – Options related to plane extraction (see iso_surface()).

  • +
+
+
Returns
+

slice_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD) # Isosurf requires single block mesh
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0.5], MPI.COMM_WORLD, elt_type='QUAD_4')
+
+
+
+ +
+
+spherical_slice(part_tree, sphere_eq, comm, containers_name=[], **options)
+

Create a spherical slice from the provided equation +\((x-x_0)^2 + (y-y_0)^2 + (z-z_0)^2 = R^2\) +on the input partitioned tree.

+

Slice is returned as an independant (2d) partitioned CGNSTree. See iso_surface() +for use restrictions and additional advices.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree to slice. Only U-NGon connectivities are managed.

  • +
  • plane_eq (list of float) – List of 4 floats \([x_0, y_0, z_0, R]\) defining the sphere equation.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output slice tree.

  • +
  • **options – Options related to plane extraction (see iso_surface()).

  • +
+
+
Returns
+

slice_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Example

+
from mpi4py import MPI
+import numpy
+import maia
+import maia.pytree as PT
+dist_tree = maia.factory.generate_dist_block(11, 'Poly', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD, preserve_orientation=True)
+
+# Add solution
+zone      = PT.get_node_from_label(part_tree, "Zone_t")
+vol_rank  = MPI.COMM_WORLD.Get_rank() * numpy.ones(PT.Zone.n_cell(zone))
+src_sol   = PT.new_FlowSolution('FlowSolution', loc='CellCenter', fields={'i_rank' : vol_rank}, parent=zone)
+
+slice_tree = maia.algo.part.spherical_slice(part_tree, [0.5,0.5,0.5,0.25], MPI.COMM_WORLD, \
+    ["FlowSolution"], elt_type="NGON_n")
+
+assert maia.pytree.get_node_from_name(slice_tree, "FlowSolution") is not None
+
+
+
+ +
+
+extract_part_from_zsr(part_tree, zsr_name, comm, containers_name=[], **options)
+

Extract the submesh defined by the provided ZoneSubRegion from the input volumic +partitioned tree.

+

Dimension of the output mesh is set up accordingly to the GridLocation of the ZoneSubRegion. +Submesh is returned as an independant partitioned CGNSTree and includes the relevant connectivities.

+

In addition, containers specified in containers_name list are transfered to the extracted tree. +Containers to be transfered can be either of label FlowSolution_t or ZoneSubRegion_t.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree from which extraction is computed. Only U-NGon +connectivities are managed.

  • +
  • zsr_name (str) – Name of the ZoneSubRegion_t node

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the fields containers to transfer +on the output extracted tree.

  • +
  • **options – Options related to the extraction.

  • +
+
+
Returns
+

extracted_tree (CGNSTree) – Extracted submesh (partitioned)

+
+
+

Extraction can be controled by the optional kwargs:

+
+
    +
  • graph_part_tool (str) – Partitioning tool used to balance the extracted zones. +Admissible values are hilbert, parmetis, ptscotch. Note that +vertex-located extractions require hilbert partitioning. Defaults to hilbert.

  • +
+
+
+

Important

+
    +
  • Input tree must be unstructured and have a ngon connectivity.

  • +
  • Partitions must come from a single initial domain on input tree.

  • +
+
+
+

See also

+

create_extractor_from_zsr() takes the same parameters, excepted containers_name, +and returns an Extractor object which can be used to exchange containers more than once through its +Extractor.exchange_fields(container_name) method.

+
+

Example

+
from   mpi4py import MPI
+import numpy as np
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, families=["WALL"], point_cloud='Vertex')
+
+# Create a ZoneSubRegion on procs for extracting odd cells
+for part_zone in PT.get_all_Zone_t(part_tree):
+  ncell       = PT.Zone.n_cell(part_zone)
+  start_range = PT.Element.Range(PT.Zone.NFaceNode(part_zone))[0]
+  point_list  = np.arange(start_range, start_range+ncell, 2, dtype=np.int32).reshape((1,-1), order='F')
+  PT.new_ZoneSubRegion(name='ZoneSubRegion', point_list=point_list, loc='CellCenter', parent=part_zone)
+
+extracted_tree = maia.algo.part.extract_part_from_zsr(part_tree, 'ZoneSubRegion', MPI.COMM_WORLD,
+                                                      containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_tree, "WallDistance") is not None
+
+
+
+ +
+
+extract_part_from_bc_name(part_tree, bc_name, comm, transfer_dataset=True, containers_name=[], **options)
+

Extract the submesh defined by the provided BC name from the input volumic +partitioned tree.

+

Behaviour and arguments of this function are similar to those of extract_part_from_zsr() +(zsr_name becomes bc_name). Optional transfer_dataset argument allows to +transfer BCDataSet from BC to the extracted mesh (default to True).

+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, families=["WALL"], point_cloud='Vertex')
+
+extracted_bc = maia.algo.part.extract_part_from_bc_name(part_tree, \
+               'wall', MPI.COMM_WORLD, containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_bc, "WallDistance") is not None
+
+
+
+ +
+
+

Interpolations

+
+
+interpolate_from_part_trees(src_tree, tgt_tree, comm, containers_name, location, **options)
+

Interpolate fields between two partitionned trees.

+

For now, interpolation is limited to lowest order: target points take the value of the +closest point (or their englobing cell, depending of choosed options) in the source mesh. +Interpolation strategy can be controled thought the options kwargs:

+
    +
  • strategy (default = ‘Closest’) – control interpolation method

    +
      +
    • ‘Closest’ : Target points take the value of the closest source cell center.

    • +
    • ‘Location’ : Target points take the value of the cell in which they are located. +Unlocated points have take a NaN value.

    • +
    • ‘LocationAndClosest’ : Use ‘Location’ method and then ‘ClosestPoint’ method +for the unlocated points.

    • +
    +
  • +
  • loc_tolerance (default = 1E-6) – Geometric tolerance for Location method.

  • +
+
+

Important

+
    +
  • Source fields must be located at CellCenter.

  • +
  • Source tree must be unstructured and have a ngon connectivity.

  • +
+
+
+

See also

+

create_interpolator_from_part_trees() takes the same parameters, excepted containers_name, +and returns an Interpolator object which can be used to exchange containers more than once through its +Interpolator.exchange_fields(container_name) method.

+
+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned. Only U-NGon connectivities are managed.

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned. Structured or U-NGon connectivities are managed.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the source FlowSolution_t nodes to transfer.

  • +
  • location ({'CellCenter', 'Vertex'}) – Expected target location of the fields.

  • +
  • **options – Options related to interpolation strategy

  • +
+
+
+

Example

+
import mpi4py
+import numpy
+import maia
+import maia.pytree as PT
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.factory.generate_dist_block(11, 'Poly', comm)
+dist_tree_tgt = maia.factory.generate_dist_block(20, 'Poly', comm)
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+# Create fake solution
+zone = maia.pytree.get_node_from_label(part_tree_src, "Zone_t")
+src_sol = maia.pytree.new_FlowSolution('FlowSolution', loc='CellCenter', parent=zone)
+PT.new_DataArray("Field", numpy.random.rand(PT.Zone.n_cell(zone)), parent=src_sol)
+
+maia.algo.part.interpolate_from_part_trees(part_tree_src, part_tree_tgt, comm,\
+    ['FlowSolution'], 'Vertex')
+tgt_sol = PT.get_node_from_name(part_tree_tgt, 'FlowSolution')
+assert tgt_sol is not None and PT.Subset.GridLocation(tgt_sol) == 'Vertex'
+
+
+
+ +
+
+centers_to_nodes(tree, comm, containers_name=[], **options)
+

Create Vertex located FlowSolution_t from CellCenter located FlowSolution_t.

+

Interpolation is based on Inverse Distance Weighting +(IDW) method: +each cell contributes to each of its vertices with a weight computed from the distance +between the cell isobarycenter and the vertice. The method can be tuned with +the following kwargs:

+
    +
  • idw_power (float, default = 1) – Power to which the cell-vertex distance is elevated.

  • +
  • cross_domain (bool, default = True) – If True, vertices located at domain +interfaces also receive data from the opposite domain cells. This parameter does not +apply to internal partitioning interfaces, which are always crossed.

  • +
+
+
Parameters
+
    +
  • tree (CGNSTree) – Partionned tree. Only unstructured connectivities are managed.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer.

  • +
  • **options – Options related to interpolation, see above.

  • +
+
+
+
+

See also

+

A CenterToNode object can be instanciated with the same parameters, excluding containers_name, +and then be used to move containers more than once with its +move_fields(container_name) method.

+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Init a FlowSolution located at Cells
+for part in PT.get_all_Zone_t(part_tree):
+  cell_center = maia.algo.part.compute_cell_center(part)
+  fields = {'ccX': cell_center[0::3], 'ccY': cell_center[1::3], 'ccZ': cell_center[2::3]}
+  PT.new_FlowSolution('FSol', loc='CellCenter', fields=fields, parent=part)
+
+maia.algo.part.centers_to_nodes(part_tree, comm, ['FSol'])
+
+for part in PT.get_all_Zone_t(part_tree):
+  vtx_sol = PT.get_node_from_name(part, 'FSol#Vtx')
+  assert PT.Subset.GridLocation(vtx_sol) == 'Vertex'
+
+
+
+ +
+
+
+

Generic algorithms

+

The following algorithms applies on maia distributed or partitioned trees

+
+
+transform_affine(t, rotation_center=array([0., 0., 0.]), rotation_angle=array([0., 0., 0.]), translation=array([0., 0., 0.]), apply_to_fields=False)
+

Apply the affine transformation to the coordinates of the given zone.

+

Input zone(s) can be either structured or unstructured, but must have cartesian coordinates. +Transformation is defined by

+
+\[\tilde v = R \cdot (v - c) + c + t\]
+

where c, t are the rotation center and translation vector and R is the rotation matrix.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher.

  • +
  • rotation_center (array) – center coordinates of the rotation

  • +
  • rotation_angler (array) – angles of the rotation

  • +
  • translation (array) – translation vector components

  • +
  • apply_to_fields (bool, optional) – if True, apply the rotation vector to the vectorial fields found under +following nodes : FlowSolution_t, DiscreteData_t, ZoneSubRegion_t, BCDataset_t. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+zone = maia.pytree.get_all_Zone_t(dist_tree)[0]
+
+maia.algo.transform_affine(zone, translation=[3,0,0])
+
+
+
+ +
+
+pe_to_nface(t, comm=None, removePE=False)
+

Create a NFace node from a NGon node with ParentElements.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Distributed or Partitioned tree (or sequences of) +starting at Zone_t level or higher.

  • +
  • comm (MPIComm) – MPI communicator, mandatory only for distributed zones

  • +
  • remove_PE (bool, optional) – If True, remove the ParentElements node. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+tree = maia.factory.generate_dist_block(6, 'Poly', MPI.COMM_WORLD)
+
+for zone in maia.pytree.get_all_Zone_t(tree):
+  maia.algo.pe_to_nface(zone, MPI.COMM_WORLD)
+  assert maia.pytree.get_child_from_name(zone, 'NFaceElements') is not None
+
+
+
+ +
+
+nface_to_pe(t, comm=None, removeNFace=False)
+

Create a ParentElements node in the NGon node from a NFace node.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Distributed or Partitioned tree (or sequences of) +starting at Zone_t level or higher.

  • +
  • comm (MPIComm) – MPI communicator, mandatory only for distributed zones

  • +
  • removeNFace (bool, optional) – If True, remove the NFace node. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+tree = maia.factory.generate_dist_block(6, 'NFace_n', MPI.COMM_WORLD)
+
+maia.algo.nface_to_pe(tree, MPI.COMM_WORLD)
+assert maia.pytree.get_node_from_name(tree, 'ParentElements') is not None
+
+
+
+ +
+
+

Sequential algorithms

+

The following algorithms applies on regular pytrees.

+
+
+poly_new_to_old(tree, full_onera_compatibility=True)
+

Transform a tree with polyhedral unstructured connectivity with new CGNS 4.x conventions to old CGNS 3.x conventions.

+

The tree is modified in place.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree described with new CGNS convention.

  • +
  • full_onera_compatibility (bool) – if True, shift NFace and ParentElements ids to begin at 1, irrespective of the NGon and NFace ElementRanges, and make the NFace connectivity unsigned

  • +
+
+
+
+ +
+
+poly_old_to_new(tree)
+

Transform a tree with polyhedral unstructured connectivity with old CGNS 3.x conventions to new CGNS 4.x conventions.

+

The tree is modified in place.

+

This function accepts trees with old ONERA conventions where NFace and ParentElements ids begin at 1, irrespective of the NGon and NFace ElementRanges, and where the NFace connectivity is unsigned. The resulting tree has the correct CGNS/SIDS conventions.

+
+
Parameters
+

tree (CGNSTree) – Tree described with old CGNS convention.

+
+
+
+ +
+
+enforce_ngon_pe_local(t)
+

Shift the ParentElements values in order to make it start at 1, as requested by legacy tools.

+

The tree is modified in place.

+
+
Parameters
+

t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher.

+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/user_manual/config.html b/docs/1.1/user_manual/config.html new file mode 100644 index 00000000..efd7af5f --- /dev/null +++ b/docs/1.1/user_manual/config.html @@ -0,0 +1,334 @@ + + + + + + + + + + Configuration — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Configuration

+
+

Logging

+

Maia provides informations to the user through the loggers summarized +in the following table:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +

Logger

Purpose

Default printer

maia

Light general info

mpi_rank_0_stdout_printer

maia-warnings

Warnings

mpi_rank_0_stdout_printer

maia-errors

Errors

mpi_rank_0_stdout_printer

maia-stats

More detailed timings +and memory usage

No output

+

The easiest way to change this default configuration is to +set the environment variable LOGGING_CONF_FILE to provide a file +(e.g. logging.conf) looking like this:

+
maia          : mpi_stdout_printer        # All ranks output to sdtout
+maia-warnings :                           # No output
+maia-errors   : mpi_rank_0_stderr_printer # Rank 0 output to stderr
+maia-stats    : file_printer("stats.log") # All ranks output in the file
+
+
+

See Log management for a full description of available +printers or for instructions on how to create your own printers. +This page also explain how to dynamically change printers +directly in your Python application.

+
+
+

Exception handling

+

Maia automatically override the sys.excepthook +function to call MPI_Abort when an uncatched exception occurs. +This allow us to terminate the MPI execution and to avoid some deadlocks +if an exception is raised by a single process. +Note that this call force the global COMM_WORLD communicator to abort which +can have undesired effects if sub communicators are used.

+

This behaviour can be disabled with a call to +maia.excepthook.disable_mpi_excepthook().

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/user_manual/factory.html b/docs/1.1/user_manual/factory.html new file mode 100644 index 00000000..58dde265 --- /dev/null +++ b/docs/1.1/user_manual/factory.html @@ -0,0 +1,815 @@ + + + + + + + + + + Factory module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Factory module

+

The maia.factory module can be used to generate parallel CGNS trees. Those can +be obtained from an other kind of tree, or can be generated from user parameters.

+
+

Generation

+
+
+generate_dist_points(n_vtx, zone_type, comm, origin=array([0., 0., 0.]), max_coords=array([1., 1., 1.]))
+

Generate a distributed mesh including only cartesian points.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +of the zone is controled by the zone_type parameter:

+
    +
  • "Structured" (or "S") produces a structured zone

  • +
  • "Unstructured" (or "U") produces an unstructured zone

  • +
+

In all cases, the created zone contains only the cartesian grid coordinates; no connectivities are created. +The physical dimension of the output +is set equal to the length of the origin parameter.

+
+
Parameters
+
    +
  • n_vtx (int or array of int) – Number of vertices in each direction. Scalars +automatically extend to uniform array.

  • +
  • zone_type (str) – requested kind of points cloud

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • max_coords (array, optional) – Coordinates of the higher point of the generated mesh. Defaults to ones vector.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_points(10, 'Unstructured', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Zone.n_vtx(zone) == 10**3
+
+
+
+ +
+
+generate_dist_block(n_vtx, cgns_elmt_name, comm, origin=array([0., 0., 0.]), edge_length=1.0)
+

Generate a distributed mesh with a cartesian topology.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +and cell dimension of the zone is controled by the cgns_elmt_name parameter:

+
    +
  • "Structured" (or "S") produces a structured zone,

  • +
  • "Poly" produces an unstructured 3d zone with a NGon+PE connectivity,

  • +
  • "NFACE_n" produces an unstructured 3d zone with a NFace+NGon connectivity,

  • +
  • "NGON_n" produces an unstructured 2d zone with faces described by a NGon +node (not yet implemented),

  • +
  • Other names must be in ["TRI_3", "QUAD_4", "TETRA_4", "PYRA_5", "PENTA_6", "HEXA_8"] +and produces an unstructured 2d or 3d zone with corresponding standard elements.

  • +
+

In all cases, the created zone contains the cartesian grid coordinates and the relevant number +of boundary conditions.

+

When creating 2 dimensional zones, the +physical dimension +is set equal to the length of the origin parameter.

+
+
Parameters
+
    +
  • n_vtx (int or array of int) – Number of vertices in each direction. Scalars +automatically extend to uniform array.

  • +
  • cgns_elmt_name (str) – requested kind of elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • edge_length (float, optional) – Edge size of the generated mesh. Defaults to 1.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_block([10,20,10], 'Structured', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Zone.Type(zone) == "Structured"
+
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Element.CGNSName(PT.get_child_from_label(zone, 'Elements_t')) == 'NGON_n'
+
+dist_tree = maia.factory.generate_dist_block(10, 'TETRA_4', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Element.CGNSName(PT.get_child_from_label(zone, 'Elements_t')) == 'TETRA_4'
+
+
+
+ +
+
+generate_dist_sphere(m, cgns_elmt_name, comm, origin=array([0., 0., 0.]), radius=1.0)
+

Generate a distributed mesh with a spherical topology.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +and cell dimension of the zone is controled by the cgns_elmt_name parameter:

+
    +
  • "NFACE_n" produces an unstructured 3d zone with a NFace+NGon connectivity,

  • +
  • "NGON_n" produces an unstructured 2d zone with a NGon+Bar connectivity,

  • +
  • Other names must be in ["TRI_3", "TETRA_4"] +and produces an unstructured 2d or 3d zone with corresponding standard elements.

  • +
+

In all cases, the created zone contains the grid coordinates and the relevant number +of boundary conditions.

+

Spherical meshes are +class I geodesic polyhedra +(icosahedral). Number of vertices on the external surface is equal to +\(10m^2+2\).

+
+
Parameters
+
    +
  • m (int) – Strict. positive integer who controls the number of vertices (see above)

  • +
  • cgns_elmt_name (str) – requested kind of elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • radius (float, optional) – Radius of the generated sphere. Defaults to 1.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_sphere(10, 'TRI_3', MPI.COMM_WORLD)
+assert PT.Element.CGNSName(PT.get_node_from_label(dist_tree, 'Elements_t')) == 'TRI_3'
+
+
+
+ +
+
+distribute_tree(tree, comm, owner=None)
+

Generate a distributed tree from a standard (full) CGNS Tree.

+

Input tree can be defined on a single process (using owner = rank_id), +or a copy can be known by all the processes (using owner=None).

+

In both cases, output distributed tree will be equilibrated over all the processes.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Full (not distributed) tree.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • owner (int, optional) – MPI rank holding the input tree. Defaults to None.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+comm = MPI.COMM_WORLD
+
+if comm.Get_rank() == 0:
+  tree = maia.io.read_tree(mesh_dir/'S_twoblocks.yaml', comm)
+else:
+  tree = None
+dist_tree = maia.factory.distribute_tree(tree, comm, owner=0)
+
+
+
+ +
+
+

Partitioning

+
+
+partition_dist_tree(dist_tree, comm, **kwargs)
+

Perform the partitioning operation: create a partitioned tree from the input distributed tree.

+

The input tree can be structured or unstuctured, but hybrid meshes are not yet supported.

+
+

Important

+

Geometric information (such as boundary conditions, zone subregion, etc.) are reported +on the partitioned tree; however, data fields (BCDataSet, FlowSolution, etc.) are not +transfered automatically. See maia.transfer module.

+
+

See reference documentation for the description of the keyword arguments.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • **kwargs – Partitioning options

  • +
+
+
Returns
+

CGNSTree – partitioned cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+comm = MPI.COMM_WORLD
+i_rank, n_rank = comm.Get_rank(), comm.Get_size()
+dist_tree  = maia.factory.generate_dist_block(10, 'Poly', comm)
+
+#Basic use
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+#Crazy partitioning where each proc get as many partitions as its rank
+n_part_tot = n_rank * (n_rank + 1) // 2
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm, \
+    zone_to_parts={'Base/zone' : [1./n_part_tot for i in range(i_rank+1)]})
+assert len(maia.pytree.get_all_Zone_t(part_tree)) == i_rank+1
+
+
+
+ +
+

Partitioning options

+

Partitioning can be customized with the following keywords arguments:

+
+
+graph_part_tool
+

Method used to split unstructured blocks. Irrelevent for structured blocks.

+
+
Admissible values
+
    +
  • parmetis, ptscotch : graph partitioning methods,

  • +
  • hilbert : geometric method (only for NGon connectivities),

  • +
  • gnum : cells are dispatched according to their absolute numbering.

  • +
+
+
Default value
+

parmetis, if installed; else ptscotch, if installed; hilbert otherwise.

+
+
+
+ +
+
+zone_to_parts
+

Control the number, size and repartition of partitions. See Repartition.

+
+
Default value
+

Computed such that partitioning is balanced using +maia.factory.partitioning.compute_balanced_weights().

+
+
+
+ +
+
+part_interface_loc
+

GridLocation for the created partitions interfaces. Pre-existing interface keep their original location.

+
+
Admissible values
+

FaceCenter, Vertex

+
+
Default value
+

FaceCenter for unstructured zones with NGon connectivities; Vertex otherwise.

+
+
+
+ +
+
+reordering
+

Dictionnary of reordering options, which are used to renumber the entities in each partitioned zone. See +corresponding documentation.

+
+ +
+
+preserve_orientation
+

If True, the created interface faces are not reversed and keep their original orientation. Consequently, +NGonElements can have a zero left parent and a non zero right parent. +Only relevant for U/NGon partitions.

+
+
Default value
+

False

+
+
+
+ +
+
+dump_pdm_output
+

If True, dump the raw arrays created by paradigm in a CGNSNode at (partitioned) zone level. For debug only.

+
+
Default value
+

False

+
+
+
+ +
+
+

Repartition

+

The number, size, and repartition (over the processes) of the created partitions is +controlled through the zone_to_parts keyword argument: each process must provide a +dictionary associating the path of every distributed zone to a list of floats. +For a given distributed zone, current process will receive as many partitions +as the length of the list (missing path or empty list means no partitions); the size of these partitions +corresponds to the values in the list (expressed in fraction of the input zone). +For a given distributed zone, the sum of all the fractions across all the processes must +be 1.

+

This dictionary can be created by hand; for convenience, Maia provides three functions in the +maia.factory.partitioning module to create this dictionary.

+
+
+compute_regular_weights(tree, comm, n_part=1)
+

Compute a basic zone_to_parts repartition.

+

Each process request n_part partitions on each original zone (n_part can differ +for each proc). +The weights of all the parts produced from a given zone are homogeneous +and equal to the number of cells in the zone divided by the total +number of partitions requested for this zone.

+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
  • n_part (int,optional) – Number of partitions to produce on each zone by the proc. +Defaults to 1.

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+zone_to_parts = mpart.compute_regular_weights(dist_tree, MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_size() == 2:
+  assert zone_to_parts == {'Base/Large': [0.5], 'Base/Small': [0.5]}
+
+
+
+ +
+
+compute_balanced_weights(tree, comm, only_uniform=False)
+

Compute a well balanced zone_to_parts repartition.

+

Each process request or not partitions with heterogeneous weight on each +original zone such that:

+
    +
  • the computational load is well balanced, ie the total number of +cells per process is nearly equal,

  • +
  • the number of splits within a given zone is minimized,

  • +
  • produced partitions are not too small.

  • +
+
+

Note

+

Heterogeneous weights are not managed by ptscotch. Use parmetis as graph_part_tool +for partitioning if repartition was computed with this function, or set optional +argument only_uniform to True.

+
+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
  • only_uniform (bool, optional) – If true, an alternative balancing method is used +in order to request homogeneous weights, but load balance is less equilibrated. +Default to False.

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+comm = MPI.COMM_WORLD
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', comm)
+
+zone_to_parts = mpart.compute_balanced_weights(dist_tree, comm)
+if comm.Get_size() == 2 and comm.Get_rank() == 0:
+  assert zone_to_parts == {'Base/Large': [0.375], 'Base/Small': [1.0]}
+if comm.Get_size() == 2 and comm.Get_rank() == 1:
+  assert zone_to_parts == {'Base/Large': [0.625]}
+
+
+
+ +
+
+compute_nosplit_weights(tree, comm)
+

Compute a zone_to_parts repartition without splitting the blocks.

+

The initial blocks will be simply distributed over the available processes, +minimizing the total number of cells affected to a proc. This leads to a poor load +balancing and possibly to procs having no partitions at all.

+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+zone_to_parts = mpart.compute_nosplit_weights(dist_tree, MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_size() == 2:
+  assert len(zone_to_parts) == 1
+
+
+
+ +
+
+

Reordering options

+

For unstructured zones, the reordering options are transmitted to ParaDiGM in order to +control the renumbering of mesh entities in the partitions.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Kwarg

Admissible values

Effect

Default

cell_renum_method

“NONE”, “RANDOM”, “HILBERT”, +“CUTHILL”, “CACHEBLOCKING”, +“CACHEBLOCKING2”, “HPC”

Renumbering method for the cells

NONE

face_renum_method

“NONE”, “RANDOM”, “LEXICOGRAPHIC”

Renumbering method for the faces

NONE

vtx_renum_method

“NONE”, “SORT_INT_EXT”

Renumbering method for the vertices

NONE

n_cell_per_cache

Integer >= 0

Specific to cacheblocking

0

n_face_per_pack

Integer >= 0

Specific to cacheblocking

0

graph_part_tool

“parmetis”, “ptscotch”, +“hyperplane”

Graph partitioning library to +use for renumbering

Same as partitioning tool

+
+
+
+

Recovering from partitions

+
+
+recover_dist_tree(part_tree, comm)
+

Regenerate a distributed tree from a partitioned tree.

+

The partitioned tree should have been created using Maia, or +must at least contains GlobalNumbering nodes as defined by Maia +(see Partitioned trees).

+

The following nodes are managed : GridCoordinates, Elements, ZoneBC, ZoneGridConnectivity +FlowSolution, DiscreteData and ZoneSubRegion.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned CGNS Tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+comm = MPI.COMM_WORLD
+
+dist_tree_bck  = maia.factory.generate_dist_block(5, 'TETRA_4', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree_bck, comm)
+
+dist_tree = maia.factory.recover_dist_tree(part_tree, comm)
+assert maia.pytree.is_same_tree(dist_tree, dist_tree_bck)
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/user_manual/io.html b/docs/1.1/user_manual/io.html new file mode 100644 index 00000000..f1bc5a6d --- /dev/null +++ b/docs/1.1/user_manual/io.html @@ -0,0 +1,477 @@ + + + + + + + + + + File management — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

File management

+

Maia supports HDF5/CGNS file reading and writing, +see related documention.

+

The IO functions are provided by the maia.io module. All the high level functions +accepts a legacy parameter used to control the low level CGNS-to-hdf driver:

+
    +
  • if legacy==False (default), hdf calls are performed by the python module +h5py.

  • +
  • if legacy==True, hdf calls are performed by +Cassiopee.Converter module.

  • +
+

The requested driver should be installed on your computer as well as the +hdf5 library compiled with parallel support.

+
+

Distributed IO

+

Distributed IO is the privileged way to deal with CGNS files within your maia workflows. +Files are loaded as distributed trees, and, inversely, distributed trees can be written +into a single CGNS file.

+

High level IO operations can be performed with the two following functions, which read +or write all data they found :

+
+
+file_to_dist_tree(filename, comm, legacy=False)
+

Distributed load of a CGNS file.

+
+
Parameters
+
    +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
Returns
+

CGNSTree – Distributed CGNS tree

+
+
+
+ +
+
+dist_tree_to_file(dist_tree, filename, comm, legacy=False)
+

Distributed write to a CGNS file.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree to write

  • +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +

The example below shows how to uses these high level functions:

+
from mpi4py import MPI
+import maia
+
+# Generate a sample tree
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+# Write
+maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+# Read
+tree = maia.io.file_to_dist_tree("tree.cgns", MPI.COMM_WORLD)
+
+
+

Finer control of what is written or loaded can be achieved with the following steps:

+
    +
  • For a write operation, the easiest way to write only some nodes in +the file is to remove the unwanted nodes from the distributed tree.

  • +
  • For a read operation, the load has to be divided into the following steps:

    +
      +
    • Loading a size_tree: this tree has only the shape of the distributed data and +not the data itself.

    • +
    • Removing unwanted nodes in the size tree;

    • +
    • Fill the filtered tree from the file.

    • +
    +
  • +
+

The example below illustrate how to filter the written or loaded nodes:

+
from mpi4py import MPI
+import maia
+
+# Generate a sample tree
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+
+# Remove the nodes we do not want to write
+maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateZ') #This is a DataArray
+maia.pytree.rm_nodes_from_name(dist_tree, 'Zm*') #This is some BC nodes
+# Write
+maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+# Read
+from maia.io.cgns_io_tree import load_collective_size_tree, fill_size_tree
+dist_tree = load_collective_size_tree("tree.cgns", MPI.COMM_WORLD)
+#For now dist_tree only contains sizes : let's filter it
+maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateY') #This is a DataArray
+maia.pytree.rm_nodes_from_name(dist_tree, 'Ym*') #This is some BC nodes
+fill_size_tree(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+
+
+
+

Writing partitioned trees

+

In some cases, it may be useful to write a partitioned tree (keeping the +partitioned zones separated). This can be achieved using the following function:

+
+
+part_tree_to_file(part_tree, filename, comm, single_file=False, legacy=False)
+

Gather the partitioned zones managed by all the processes and write it in a unique +hdf container.

+

If single_file is True, one file named filename storing all the partitioned +zones is written. Otherwise, hdf links are used to produce a main file filename +linking to additional subfiles.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree

  • +
  • filename (str) – Path of the output file

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • single_file (bool) – Produce a unique file if True; use CGNS links otherwise.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.io.part_tree_to_file(part_tree, 'part_tree.cgns', MPI.COMM_WORLD)
+
+
+
+ +
+
+

Raw IO

+

For debug purpose, trees can also be read or written independently in a +sequential manner. Be aware that information added by maia such as Distribution +or GlobalNumbering nodes will not be removed.

+
+
+read_tree(filename, legacy=False)
+

Sequential load of a CGNS file.

+
+
Parameters
+

filename (str) – Path of the file

+
+
Returns
+

CGNSTree – Full (not distributed) CGNS tree

+
+
+
+ +
+
+write_tree(tree, filename, links=[], legacy=False)
+

Sequential write to a CGNS file.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree to write

  • +
  • filename (str) – Path of the file

  • +
  • links (list) – List of links to create (see SIDS-to-Python guide)

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_rank() == 0:
+  maia.io.write_tree(dist_tree, "tree.cgns")
+
+
+
+ +
+
+write_trees(tree, filename, comm, legacy=False)
+

Sequential write to CGNS files.

+

Write separate trees for each process. Rank id will be automatically +inserted in the filename.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree to write

  • +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+maia.io.write_trees(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/user_manual/transfer.html b/docs/1.1/user_manual/transfer.html new file mode 100644 index 00000000..cea7ad5b --- /dev/null +++ b/docs/1.1/user_manual/transfer.html @@ -0,0 +1,497 @@ + + + + + + + + + + Transfer module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Transfer module

+

The maia.transfer contains functions that exchange data between the +partitioned and distributed meshes.

+
+

Fields transfer

+

High level APIs allow to exchange data at CGNS Tree or Zone level. +All the provided functions take similar parameters: a distributed tree (resp. zone), +the corresponding partitioned tree (resp. list of zones), the MPI communicator and +optionally a filtering parameter.

+

The following kind of data are supported: +FlowSolution_t, DiscreteData_t, ZoneSubRegion_t and BCDataSet_t.

+

When transferring from distributed meshes to partitioned meshes, fields are supposed +to be known on the source mesh across all the ranks (according to disttree definition). +Geometric patches (such as ZoneSubRegion or BCDataSet) must exists on the relevant partitions, meaning +that only fields are transfered.

+

When transferring from partitioned meshes to distributed meshes, geometric patches may or may not exist on the +distributed zone: they will be created if needed. This allows to transfer fields that have been created +on the partitioned meshes. +It is however assumed that global data (e.g. FlowSolution) are defined on every partition.

+
+

Tree level

+

All the functions of this section operate inplace and require the following parameters:

+
    +
  • dist_tree (CGNSTree) – Distributed CGNS Tree

  • +
  • part_tree (CGNSTree) – Corresponding partitioned CGNS Tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+dist_tree_to_part_tree_all(dist_tree, part_tree, comm)
+

Transfer all the data fields from a distributed tree +to the corresponding partitioned tree.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+dist_tree = maia.io.file_to_dist_tree(filename, MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+maia.transfer.dist_tree_to_part_tree_all(dist_tree, part_tree, MPI.COMM_WORLD)
+
+zone = PT.get_all_Zone_t(part_tree)[0]
+assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_tree_to_dist_tree_all(dist_tree, part_tree, comm)
+

Transfer all the data fields from a partitioned tree +to the corresponding distributed tree.

+
+ +

In addition, the next two methods expect the parameter labels (list of str) +which allow to pick one or more kind of data to transfer from the supported labels.

+
+
+dist_tree_to_part_tree_only_labels(dist_tree, part_tree, labels, comm)
+

Transfer all the data fields of the specified labels from a distributed tree +to the corresponding partitioned tree.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['FlowSolution_t', 'ZoneSubRegion_t'], comm)
+
+zone = PT.get_all_Zone_t(part_tree)[0]
+assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None
+
+maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['BCDataSet_t'], comm)
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_tree_to_dist_tree_only_labels(dist_tree, part_tree, labels, comm)
+

Transfer all the data fields of the specified labels from a partitioned tree +to the corresponding distributed tree.

+
+ +
+
+

Zone level

+

All the functions of this section operate inplace and require the following parameters:

+
    +
  • dist_zone (CGNSTree) – Distributed CGNS Zone

  • +
  • part_zones (list of CGNSTree) – Corresponding partitioned CGNS Zones

  • +
  • comm (MPIComm) – MPI communicator

  • +
+

In addition, filtering is possible with the use of the +include_dict or exclude_dict dictionaries. These dictionaries map +each supported label to a list of cgns paths to include (or exclude). Paths +starts from the Zone_t node and ends at the targeted DataArray_t node. +Wildcard * are allowed in paths : for example, considering the following tree +structure,

+
Zone (Zone_t)
+├── FirstSolution (FlowSolution_t)
+│   ├── Pressure (DataArray_t)
+│   ├── MomentumX (DataArray_t)
+│   └── MomentumY (DataArray_t)
+├── SecondSolution (FlowSolution_t)
+│   ├── Pressure (DataArray_t)
+│   ├── MomentumX (DataArray_t)
+│   └── MomentumY (DataArray_t)
+└── SpecialSolution (FlowSolution_t)
+    ├── Density (DataArray_t)
+    └── MomentumZ (DataArray_t)
+
+
+
+
"FirstSolution/Momentum*" maps to ["FirstSolution/MomentumX", "FirstSolution/MomentumY"],
+
"*/Pressure maps to ["FirstSolution/Pressure", "SecondSolution/Pressure"], and
+
"S*/M*" maps to ["SecondSolution/MomentumX", "SecondSolution/MomentumY", "SpecialSolution/MomentumZ"].
+
+

For convenience, we also provide the magic path ['*'] meaning “everything related to this +label”.

+

Lastly, we use the following rules to manage missing label keys in dictionaries:

+
+
    +
  • For _only functions, we do not transfer any field related to the missing labels;

  • +
  • For _all functions, we do transfer all the fields related to the missing labels.

  • +
+
+
+
+dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict)
+

Transfer the data fields specified in include_dict from a distributed zone +to the corresponding partitioned zones.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+dist_zone = PT.get_all_Zone_t(dist_tree)[0]
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+part_zones = PT.get_all_Zone_t(part_tree)
+
+include_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'],
+                'BCDataSet_t'    : ['*']}
+maia.transfer.dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict)
+
+for zone in part_zones:
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+  assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is     None
+  assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_zones_to_dist_zone_only(dist_zone, part_zones, comm, include_dict)
+

Transfer the data fields specified in include_dict from the partitioned zones +to the corresponding distributed zone.

+
+ +
+
+dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict={})
+

Transfer all the data fields, excepted those specified in exclude_dict, +from a distributed zone to the corresponding partitioned zones.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+dist_zone = PT.get_all_Zone_t(dist_tree)[0]
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+part_zones = PT.get_all_Zone_t(part_tree)
+
+exclude_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'],
+                'BCDataSet_t'    : ['*']}
+maia.transfer.dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict)
+
+for zone in part_zones:
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is     None
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataY') is not None
+  assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+  assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None
+
+
+
+ +
+
+part_zones_to_dist_zone_all(dist_zone, part_zones, comm, exclude_dict={})
+

Transfer all the data fields, excepted those specified in exclude_dict, +from the partitioned zone to the corresponding distributed zone.

+
+ +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.1/user_manual/user_manual.html b/docs/1.1/user_manual/user_manual.html new file mode 100644 index 00000000..bce30421 --- /dev/null +++ b/docs/1.1/user_manual/user_manual.html @@ -0,0 +1,297 @@ + + + + + + + + + + User Manual — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

User Manual

+

Maia methods are accessible through three main modules :

+
    +
  • Factory allows to generate Maia trees, generally from another kind of tree +(e.g. the partitioning operation). Factory functions return a new tree +whose nature generally differs from the input tree.

  • +
  • Algo is the main Maia module and provides parallel algorithms to be applied on Maia trees. +Some algorithms are only available for distributed trees +and some are only available for partitioned trees. +A few algorithms are implemented for both kind +of trees and are thus directly accessible through the algo module.

    +

    Algo functions either modify their input tree inplace, or return some data, but they do not change the nature +of the tree.

    +
  • +
  • Transfer is a small module allowing to transfer data between Maia trees. A transfer function operates +on two existing trees and enriches the destination tree with data fields of the source tree.

  • +
+

Using Maia trees in your application often consists in chaining functions from these different modules.

+../_images/workflow.svg

A typical workflow could be:

+
    +
  1. Load a structured tree from a file, which produces a dist tree.

  2. +
  3. Apply some distributed algorithms to this tree: for example a structured to unstructured conversion (algo.dist module).

  4. +
  5. Generate a corresponding partitionned tree (factory module).

  6. +
  7. Apply some partitioned algorithms to the part tree, such as wall distance computation (algo.part module), +and even call you own tools (e.g. a CFD solver)

  8. +
  9. Transfer the resulting fields to the dist tree (transfer module).

  10. +
  11. Save the updated dist tree to disk.

  12. +
+

This user manuel describes the main functions available in each module.

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.1 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/.buildinfo b/docs/1.2/.buildinfo new file mode 100644 index 00000000..48804b96 --- /dev/null +++ b/docs/1.2/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 0e05f221583dba3f1f28ac9b34de6909 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/1.2/.doctrees/developer_manual/algo_description.doctree b/docs/1.2/.doctrees/developer_manual/algo_description.doctree new file mode 100644 index 00000000..ae355173 Binary files /dev/null and b/docs/1.2/.doctrees/developer_manual/algo_description.doctree differ diff --git a/docs/1.2/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree b/docs/1.2/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree new file mode 100644 index 00000000..b4a2ba55 Binary files /dev/null and b/docs/1.2/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree differ diff --git a/docs/1.2/.doctrees/developer_manual/developer_manual.doctree b/docs/1.2/.doctrees/developer_manual/developer_manual.doctree new file mode 100644 index 00000000..9727f928 Binary files /dev/null and b/docs/1.2/.doctrees/developer_manual/developer_manual.doctree differ diff --git a/docs/1.2/.doctrees/developer_manual/logging.doctree b/docs/1.2/.doctrees/developer_manual/logging.doctree new file mode 100644 index 00000000..be96deb2 Binary files /dev/null and b/docs/1.2/.doctrees/developer_manual/logging.doctree differ diff --git a/docs/1.2/.doctrees/developer_manual/maia_dev/conventions.doctree b/docs/1.2/.doctrees/developer_manual/maia_dev/conventions.doctree new file mode 100644 index 00000000..ce9c34f6 Binary files /dev/null and b/docs/1.2/.doctrees/developer_manual/maia_dev/conventions.doctree differ diff --git a/docs/1.2/.doctrees/developer_manual/maia_dev/development_workflow.doctree b/docs/1.2/.doctrees/developer_manual/maia_dev/development_workflow.doctree new file mode 100644 index 00000000..731aaaf2 Binary files /dev/null and b/docs/1.2/.doctrees/developer_manual/maia_dev/development_workflow.doctree differ diff --git a/docs/1.2/.doctrees/environment.pickle b/docs/1.2/.doctrees/environment.pickle new file mode 100644 index 00000000..64b16fe2 Binary files /dev/null and b/docs/1.2/.doctrees/environment.pickle differ diff --git a/docs/1.2/.doctrees/index.doctree b/docs/1.2/.doctrees/index.doctree new file mode 100644 index 00000000..70dbe376 Binary files /dev/null and b/docs/1.2/.doctrees/index.doctree differ diff --git a/docs/1.2/.doctrees/installation.doctree b/docs/1.2/.doctrees/installation.doctree new file mode 100644 index 00000000..6f416c11 Binary files /dev/null and b/docs/1.2/.doctrees/installation.doctree differ diff --git a/docs/1.2/.doctrees/introduction/introduction.doctree b/docs/1.2/.doctrees/introduction/introduction.doctree new file mode 100644 index 00000000..13005ab6 Binary files /dev/null and b/docs/1.2/.doctrees/introduction/introduction.doctree differ diff --git a/docs/1.2/.doctrees/license.doctree b/docs/1.2/.doctrees/license.doctree new file mode 100644 index 00000000..09d331ba Binary files /dev/null and b/docs/1.2/.doctrees/license.doctree differ diff --git a/docs/1.2/.doctrees/quick_start.doctree b/docs/1.2/.doctrees/quick_start.doctree new file mode 100644 index 00000000..24da413f Binary files /dev/null and b/docs/1.2/.doctrees/quick_start.doctree differ diff --git a/docs/1.2/.doctrees/related_projects.doctree b/docs/1.2/.doctrees/related_projects.doctree new file mode 100644 index 00000000..daf69886 Binary files /dev/null and b/docs/1.2/.doctrees/related_projects.doctree differ diff --git a/docs/1.2/.doctrees/releases/release_notes.doctree b/docs/1.2/.doctrees/releases/release_notes.doctree new file mode 100644 index 00000000..845ecbc6 Binary files /dev/null and b/docs/1.2/.doctrees/releases/release_notes.doctree differ diff --git a/docs/1.2/.doctrees/user_manual/algo.doctree b/docs/1.2/.doctrees/user_manual/algo.doctree new file mode 100644 index 00000000..e00d09a0 Binary files /dev/null and b/docs/1.2/.doctrees/user_manual/algo.doctree differ diff --git a/docs/1.2/.doctrees/user_manual/config.doctree b/docs/1.2/.doctrees/user_manual/config.doctree new file mode 100644 index 00000000..0dda8b2d Binary files /dev/null and b/docs/1.2/.doctrees/user_manual/config.doctree differ diff --git a/docs/1.2/.doctrees/user_manual/factory.doctree b/docs/1.2/.doctrees/user_manual/factory.doctree new file mode 100644 index 00000000..6ebb69b3 Binary files /dev/null and b/docs/1.2/.doctrees/user_manual/factory.doctree differ diff --git a/docs/1.2/.doctrees/user_manual/io.doctree b/docs/1.2/.doctrees/user_manual/io.doctree new file mode 100644 index 00000000..7edb9a06 Binary files /dev/null and b/docs/1.2/.doctrees/user_manual/io.doctree differ diff --git a/docs/1.2/.doctrees/user_manual/transfer.doctree b/docs/1.2/.doctrees/user_manual/transfer.doctree new file mode 100644 index 00000000..096345f7 Binary files /dev/null and b/docs/1.2/.doctrees/user_manual/transfer.doctree differ diff --git a/docs/1.2/.doctrees/user_manual/user_manual.doctree b/docs/1.2/.doctrees/user_manual/user_manual.doctree new file mode 100644 index 00000000..c9a6900a Binary files /dev/null and b/docs/1.2/.doctrees/user_manual/user_manual.doctree differ diff --git a/docs/1.2/.nojekyll b/docs/1.2/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/1.2/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns b/docs/1.2/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns new file mode 100644 index 00000000..85412b65 Binary files /dev/null and b/docs/1.2/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns differ diff --git a/docs/1.2/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns b/docs/1.2/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns new file mode 100644 index 00000000..21beb3c1 Binary files /dev/null and b/docs/1.2/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns differ diff --git a/docs/1.2/_images/data_dist.svg b/docs/1.2/_images/data_dist.svg new file mode 100644 index 00000000..5161b893 --- /dev/null +++ b/docs/1.2/_images/data_dist.svg @@ -0,0 +1,265 @@ + + + +(a)(b) diff --git a/docs/1.2/_images/data_dist_gnum.svg b/docs/1.2/_images/data_dist_gnum.svg new file mode 100644 index 00000000..0270c247 --- /dev/null +++ b/docs/1.2/_images/data_dist_gnum.svg @@ -0,0 +1,229 @@ + + + +dist=[0,3,5,7] diff --git a/docs/1.2/_images/data_full.svg b/docs/1.2/_images/data_full.svg new file mode 100644 index 00000000..773e4861 --- /dev/null +++ b/docs/1.2/_images/data_full.svg @@ -0,0 +1,161 @@ + + + +1234567 diff --git a/docs/1.2/_images/data_part.svg b/docs/1.2/_images/data_part.svg new file mode 100644 index 00000000..702ee2cc --- /dev/null +++ b/docs/1.2/_images/data_part.svg @@ -0,0 +1,307 @@ + + + +(a)(b)(c) diff --git a/docs/1.2/_images/data_part_gnum.svg b/docs/1.2/_images/data_part_gnum.svg new file mode 100644 index 00000000..5f8bad52 --- /dev/null +++ b/docs/1.2/_images/data_part_gnum.svg @@ -0,0 +1,252 @@ + + + +LN_to_GN=[1,6,4,3]LN_to_GN=[2,7,5]LN_to_GN=[3,5] diff --git a/docs/1.2/_images/dist_mesh.svg b/docs/1.2/_images/dist_mesh.svg new file mode 100644 index 00000000..cab59cbc --- /dev/null +++ b/docs/1.2/_images/dist_mesh.svg @@ -0,0 +1,491 @@ + + + +image/svg+xml1 +4 +3 +2 +6 +5 +1 +9 +8 +7 +2 +10 +11 +6 +3 +12 +4 +5 + \ No newline at end of file diff --git a/docs/1.2/_images/dist_mesh_arrays.svg b/docs/1.2/_images/dist_mesh_arrays.svg new file mode 100644 index 00000000..99abc251 --- /dev/null +++ b/docs/1.2/_images/dist_mesh_arrays.svg @@ -0,0 +1,1073 @@ + + + +image/svg+xmlCoordinateX +CoordinateY +Connectivity +1 +2 +3 +4 +5 +6 +1 2 3 4 5 6 7 8 9 +10 +11 +12 +1 9 +10 +2 +2 +10 +12 +3 8 7 6 +11 +9 8 +111011 +6 5 4 +10 +11 +4 +12 + diff --git a/docs/1.2/_images/dist_part_LN_to_GN.svg b/docs/1.2/_images/dist_part_LN_to_GN.svg new file mode 100644 index 00000000..91c01db2 --- /dev/null +++ b/docs/1.2/_images/dist_part_LN_to_GN.svg @@ -0,0 +1,2169 @@ + + + +image/svg+xml1 +4 +3 +2 +6 +5 +1 +9 +8 +7 +2 +10 +11 +6 +3 +12 +4 +5 +8 +7 +6 +5 +4 +1 +2 +3 +6 +7 +8 +3 +4 +5 +1 +2 +2 +1 +3 +1 +2 +3 +LN to GN of +vertices +LN +to +GN of +elements +1 2 3 4 5 6 7 8 +1 2 3 4 5 6 7 8 +2 1 6 +3 +12 +4 +11 +10 +2 9 1 +4 5 +10 +11 +6 9 8 7 +4 3 5 +1 +2 +3 +1 +2 +3 +Distributed +mesh +Sub +- +mesh +0 +Sub +- +mesh +1 +Sub +- +mesh +0 +Sub +- +mesh +1 + \ No newline at end of file diff --git a/docs/1.2/_images/dist_tree.png b/docs/1.2/_images/dist_tree.png new file mode 100644 index 00000000..d62c4cb4 Binary files /dev/null and b/docs/1.2/_images/dist_tree.png differ diff --git a/docs/1.2/_images/dist_tree_expl.png b/docs/1.2/_images/dist_tree_expl.png new file mode 100644 index 00000000..ae830142 Binary files /dev/null and b/docs/1.2/_images/dist_tree_expl.png differ diff --git a/docs/1.2/_images/full_mesh.svg b/docs/1.2/_images/full_mesh.svg new file mode 100644 index 00000000..6dc266fe --- /dev/null +++ b/docs/1.2/_images/full_mesh.svg @@ -0,0 +1,509 @@ + + + +image/svg+xml1 + +4 + +3 + +2 + +6 + +5 + +1 + +9 + +8 + +7 + +2 + +10 + +11 + +6 + +3 + +12 + +4 + +5 + + \ No newline at end of file diff --git a/docs/1.2/_images/part_mesh.svg b/docs/1.2/_images/part_mesh.svg new file mode 100644 index 00000000..a008bd94 --- /dev/null +++ b/docs/1.2/_images/part_mesh.svg @@ -0,0 +1,598 @@ + + + +image/svg+xml8 +7 +6 +5 +4 +1 +2 +3 +6 +7 +8 +3 +4 +5 +1 +2 +2 +1 +3 +1 +2 +3 + \ No newline at end of file diff --git a/docs/1.2/_images/part_mesh_arrays.svg b/docs/1.2/_images/part_mesh_arrays.svg new file mode 100644 index 00000000..110edd78 --- /dev/null +++ b/docs/1.2/_images/part_mesh_arrays.svg @@ -0,0 +1,888 @@ + + + +image/svg+xmlCoordinateX +CoordinateY +Connectivity +1 +2 +3 +1 +2 +3 +1 2 3 4 5 6 7 8 +1 2 3 4 5 6 7 8 +6 5 2 1 8 7 5 6 +5 4 3 2 +6 7 4 3 7 8 5 4 +4 5 2 1 + diff --git a/docs/1.2/_images/part_tree.png b/docs/1.2/_images/part_tree.png new file mode 100644 index 00000000..eced07ef Binary files /dev/null and b/docs/1.2/_images/part_tree.png differ diff --git a/docs/1.2/_images/part_tree_expl.png b/docs/1.2/_images/part_tree_expl.png new file mode 100644 index 00000000..843ae4e3 Binary files /dev/null and b/docs/1.2/_images/part_tree_expl.png differ diff --git a/docs/1.2/_images/qs_basic.png b/docs/1.2/_images/qs_basic.png new file mode 100644 index 00000000..b17d8907 Binary files /dev/null and b/docs/1.2/_images/qs_basic.png differ diff --git a/docs/1.2/_images/qs_pycgns.png b/docs/1.2/_images/qs_pycgns.png new file mode 100644 index 00000000..54176820 Binary files /dev/null and b/docs/1.2/_images/qs_pycgns.png differ diff --git a/docs/1.2/_images/qs_workflow.png b/docs/1.2/_images/qs_workflow.png new file mode 100644 index 00000000..e4b26743 Binary files /dev/null and b/docs/1.2/_images/qs_workflow.png differ diff --git a/docs/1.2/_images/tree_seq.png b/docs/1.2/_images/tree_seq.png new file mode 100644 index 00000000..9fcff12c Binary files /dev/null and b/docs/1.2/_images/tree_seq.png differ diff --git a/docs/1.2/_images/workflow.svg b/docs/1.2/_images/workflow.svg new file mode 100644 index 00000000..dd21687a --- /dev/null +++ b/docs/1.2/_images/workflow.svg @@ -0,0 +1,669 @@ + + + +image/svg+xmldist_tree +Field +transfers +Distributed +algorithms +zone +merges +, +connectivity +transformations… +Partitioned +algorithms +Solver +, +wall +distances, +HPC +renumbering + +Part_tree +Part_tree +part_tree +Creation +by +partitioning +CGNS +File + \ No newline at end of file diff --git a/docs/1.2/_sources/developer_manual/algo_description.rst.txt b/docs/1.2/_sources/developer_manual/algo_description.rst.txt new file mode 100644 index 00000000..1e5eb7a7 --- /dev/null +++ b/docs/1.2/_sources/developer_manual/algo_description.rst.txt @@ -0,0 +1,10 @@ +Algorithms description +====================== + +This section provides a detailed description of some algorithms. + +.. toctree:: + :maxdepth: 1 + + algo_description/elements_to_ngons.rst + diff --git a/docs/1.2/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt b/docs/1.2/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt new file mode 100644 index 00000000..4827afef --- /dev/null +++ b/docs/1.2/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt @@ -0,0 +1,230 @@ +.. _elements_to_ngons_impl: + +elements_to_ngons +================= + +Description +----------- + +.. code-block:: python + + maia.algo.dist.elements_to_ngons(dist_tree_elts, comm) + +Take a **distributed** :cgns:`CGNSTree_t` or :cgns:`CGNSBase_t`, and transform it into a **distributed** :cgns:`NGon/NFace` mesh. The tree is modified in-place. + +Example +------- + +.. literalinclude:: ../../user_manual/snippets/test_algo.py + :start-after: #elements_to_ngons@start + :end-before: #elements_to_ngons@end + :dedent: 2 + +Arguments +--------- + +:code:`dist_tree_elts` + a :ref:`distributed tree ` with + * unstructured zones that contain regular element sections (i.e. not NGON, not NFACE and not MIXED) + * the :ref:`Maia tree ` property + +:code:`comm` + a communicator over which :code:`elements_to_ngons` is called + +Output +------ + +The tree is modified in-place. The regular element sections are replaced by: + +* a NGon section with: + + * An :cgns:`ElementStartOffset`/:cgns:`ElementConnectivity` node describing: + + * first the external faces in exactly the same order as they were (in particular, gathered by face type) + * then the internal faces, also gathered by face type + + * a :cgns:`ParentElements` node and a :cgns:`ParentElementsPosition` node + +* a NFace section with cells ordered as they were, with ids only shifted by the number of interior faces + + +Parallelism +----------- + +:code:`elements_to_ngons` is a collective function. + +Complexity +---------- + +With :math:`N` the number of zones, :math:`n_i` the number of elements of zone :math:`i`, :math:`n_{f,i}` its number of interior faces, and :math:`K` the number of processes + +Sequential time + :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i \cdot log(n_i) \right)` + + The cost is dominated by the sorting of faces (interior and exterior) used to spot identical ones. + +Parallel time + :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i/K \cdot log(n_i) \right)` + + The parallel distributed sort algorithm consists of three steps: + + 1. A partitioning step that locally gather faces into :math:`K` buckets that are of equal global size. This uses a :math:`K`-generalized variant of `quickselect `_ that is of complexity :math:`\displaystyle \mathcal{O} \left( n_i/K \cdot log(K) \right)` + 2. An all_to_all exchange step that gather the :math:`K` buckets on the :math:`K` processes. This step is not accounted for here (see below) + 3. A local sorting step that is :math:`\displaystyle \mathcal{O} \left( n_i/K \cdot log(n_i/K) \right)` + + If we sum up step 1 and 3, we get + +.. math:: + + \begin{equation} \label{eq1} + \begin{split} + \textrm{C}_\textrm{parallel_sort} & = \mathcal{O} \left( n_i/K \cdot log(K) + n_i/K \cdot log(n_i/K) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot \left( log(K) + log(n_i/K) \right) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot log(n_i) \right) + \end{split} + \end{equation} + +Theoretical scaling + :math:`\textrm{Speedup} = K` + + Experimentally, the scaling is much worse - under investigation. + + Note: the speedup is computed by :math:`\textrm{Speedup} = t_s / t_p` where :math:`t_s` is the sequential time and :math:`t_p` the parallel time. A speedup of :math:`K` is perfect, a speedup lower than :math:`1` means that sequential execution is faster. + +Peak memory + Approximately :math:`\displaystyle \sum_{i=0}^N 2 n_i + n_{f,i}` + + This is the size of the input tree + the output tree. :math:`n_i` is counted twice: once for the input element connectivity, once for the output NFace connectivity + +Size of communications + Approximately :math:`\displaystyle \sum_{i=0}^N 3 n_{f,i} + n_i` **all_to_all** calls + + For each zone, one **all_to_all** call to sort interior faces, one to send back the faces to the NFace, one to concatenate all faces, one to concatenate all cells + +Number of communication calls + Should be :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N log(n_i/K) \right)` + + The number of communications is constant, except for the algorithm finding a balanced distribution of interior faces + +Note + In practice, :math:`n_{f,i}` varies from :math:`2 n_i` (tet-dominant meshes) to :math:`3 n_i` (hex-dominant meshes). + +Algorithm explanation +--------------------- + +.. code-block:: c++ + + maia::elements_to_ngons(cgns::tree& dist_zone, MPI_Comm comm) + +The algorithm is divided in two steps: + +1. Generate interior faces, insert them between exterior faces and cells. During the process, also compute the parent information (:cgns:`ParentElements` and :cgns:`ParentElementsPosition`) and add the :cgns:`CellFace` connectivity to cells. All elements keep their standard element types, in particular interior faces are inserted by section type (e.g. there will be a :cgns:`TRI_3_interior` node and a :cgns:`QUAD_4_interior` node, not a :cgns:`NGon` node) +2. Transform all sections into :cgns:`NGon/NFace` + +Generate interior faces +^^^^^^^^^^^^^^^^^^^^^^^ + +Simplified algorithm +"""""""""""""""""""" + +Let us look at a simplified sequential algorithm first: + +.. code-block:: text + + 1. For all element sections (3D and 2D): + Generate faces + => FaceVtx arrays (one for Tris, one for Quads) + => Associated Parent and ParentPosition arrays + (only one parent by element at this stage) + Interior faces are generated twice (by their two parent cells) + Exterior faces are generated twice (by a parent cell and a boundary face) + + 2. For each element kind in {Tri,Quad} + Sort the FaceVtx array + (ordering: lexicographical comparison of vertices) + Sort the parent arrays accordingly + => now each face appears consecutively exactly twice + for interior faces, + the FaceVtx is always inverted between the two occurences + for exterior faces, + it depends on the normal convention + + Note that interior and exterior faces can be distinguised + by looking at the id of their parents + + 3. For each interior face appearing twice: + Create an interior face section where: + the first FaceVtx is kept + the two parents are stored + 4. For each exterior face appearing twice: + One of the face is the original boundary section face, + one was generated from the joint cell + Send back to the original boundary face its parent face id and position + => store the parent information of the boundary face + +Parallel algorithm +"""""""""""""""""" + +The algorithm is very similar to the sequential one. We need to modify two operations: + +Sorting of the FaceVtx array (step 2) + The parallel sorting is done in three steps: + + 1. apply a partial sort :cpp:`std_e::sort_by_rank` that will determine the rank of each FaceVtx + 2. call an :cpp:`all_to_all` communication step that sends each connectivity to its rank, based on the information of the previous step + 3. sort each received FaceVtx locally + +Send back boundary parents and position to the original boundary faces (step 4) + Since the original face can be remote, this is a parallel communication operation using :cpp:`std_e::scatter` + +Computation of the CellFace +""""""""""""""""""""""""""" + +After step 2, we have all the faces exactly once, with their parent ids and parent positions. +We can then compute the CellFace of each cell section by the following algorithm: + +.. code-block:: text + + For each cell section: + pre-allocate the CellFace array + (its size is n_cell_in_section * n_face_of_cell_type) + view it as a global distributed array + For each unique face: + For each of its parent cells (could be one or two): + send the parent cell the id of the face and its position + insert the result in the CellFace array + +As previously, the send operation uses a **scatter** pattern + +Transform all sections into NGon/NFace +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Thanks to the previous algorithm, we have: + +* all exterior and interior faces with their parent information +* the CellFace connectivity of the cell sections + +Elements are ordered in something akin to this: + +* boundary tris +* boundary quads +* internal tris +* internal quads + +* tetras +* pyras +* prisms +* hexas + +The algorithm then just needs to: + +* concatenate all FaceVtx of the faces into a :cgns:`NGon` node and add a :cgns:`ElementStartOffset` +* concatenate all CellFace of the cells into a :cgns:`NFace` node and add a :cgns:`ElementStartOffset` + +Design alternatives +------------------- + +The final step of the computation involves concatenating faces and cells global section by global section. This requires two heavyweight **all_to_all** calls. An alternative would be to concatenate locally. This would imply two trade-offs: + +* the faces and cells would then not be globally gathered by type, and the exterior faces would not be first +* all the :cgns:`PointLists` (including those where :cgns:`GridLocation=FaceCenter`) would have to be shifted diff --git a/docs/1.2/_sources/developer_manual/developer_manual.rst.txt b/docs/1.2/_sources/developer_manual/developer_manual.rst.txt new file mode 100644 index 00000000..ab887981 --- /dev/null +++ b/docs/1.2/_sources/developer_manual/developer_manual.rst.txt @@ -0,0 +1,13 @@ +.. _dev_manual: + +################ +Developer Manual +################ + +.. toctree:: + :maxdepth: 1 + + logging + maia_dev/conventions + maia_dev/development_workflow + algo_description diff --git a/docs/1.2/_sources/developer_manual/logging.rst.txt b/docs/1.2/_sources/developer_manual/logging.rst.txt new file mode 100644 index 00000000..9592d08f --- /dev/null +++ b/docs/1.2/_sources/developer_manual/logging.rst.txt @@ -0,0 +1,122 @@ +.. _logging: + +Log management +============== + + +Loggers +------- + +A **logger** is a global object where an application or a library can log to. +It can be declared with + +.. literalinclude:: snippets/test_logging.py + :start-after: #add_logger@start + :end-before: #add_logger@end + :dedent: 2 + +or with the equivalent C++ code + +.. code-block:: C++ + + #include "std_e/logging/log.hpp" + + std_e::add_logger("my_logger"); + +A logger declared in Python is available in C++ and vice-versa: we do not need to create it twice. + +.. note:: Loggers are available to pure C/C++ programs (in particular, test programs that do not use the Python interpreter). + +It can then be referred to by its name. If we want to log a string to ``my_logger``, we will do it like so: + +.. literalinclude:: snippets/test_logging.py + :start-after: #log@start + :end-before: #log@end + :dedent: 2 + +.. code-block:: C++ + + #include "std_e/logging/log.hpp" + + std_e::log("my_logger", "my message"); + +Typically, an application will use several loggers to log messages by themes. By convention, the name of a logger in the application ``my_app`` should begin with ``my_app``. For instance, loggers can be named: ``my_app``, ``my_app-stats``, ``my_app-errors``... + +Loggers are both + +- developper-oriented, that is, when a developper wants to output a message, he should select a logger (existing or not) encompassing the theme of the message he wants to log, +- user-oriented, because a user can choose what logger he wants to listen to. + + + +Printers +-------- + +By itself, a logger does not do anything with the messages it receives. For that, we need to attach **printers** to a logger that will handle its messages. + +For instance, we can attach a printer that will output the message to the console: + +.. literalinclude:: snippets/test_logging.py + :start-after: #add_printer@start + :end-before: #add_printer@end + :dedent: 2 + +Printers are user-oriented: it is the user of the application who decides what he wants to do with the message that each logger is receiving, by attaching none or several printers to a logger. + +Available printers +^^^^^^^^^^^^^^^^^^ + +stdout_printer, stderr_printer + output messages to the console (respectively stdout and stderr) + +mpi_stdout_printer, mpi_stderr_printer + output messages to the console, but prefix them by ``MPI.COMM_WORLD.Get_rank()`` + +mpi_rank_0_stdout_printer, mpi_rank_0_stderr_printer + output messages to the console, if ``MPI.COMM_WORLD.Get_rank()==0`` + +file_printer('my_file.extension') + output messages to file ``my_file.extension`` + +mpi_file_printer('my_file.extension') + output messages to files ``my_file.{rk}.extension``, with ``rk = MPI.COMM_WORLD.Get_rank()`` + +.. note:: + MPI-aware printers use ``MPI.COMM_WORLD`` rather than a local communicator. While the latter would be more correct, it would imply local instanciation of printers, at the time or after a new communicator is created. While this is certainly doable, up until now we feel that it does not worth the burden. But if the need arises, they can still be added. + +Create your own printer +^^^^^^^^^^^^^^^^^^^^^^^ + +Any Python type can be used as a printer as long as it provides a ``log`` method that accepts a string argument. + +.. literalinclude:: snippets/test_logging.py + :start-after: #create_printer@start + :end-before: #create_printer@end + :dedent: 2 + + +Configuration file +------------------ + +Loggers are associated to default printers. While they can be configured anytime in the Python scripts, most of the time, reading a configuration file at the start of the program is enough. The program will try to read a configuration file if the environment variable ``LOGGING_CONF_FILE`` is set. A logging configuration file looks like this: + + +.. code-block:: Text + + my_app : mpi_stdout_printer + my_app-my_theme : mpi_file_printer('my_theme.log') + +For developpers, a logging file ``logging.conf`` with loggers and default printers is put in the ``build/`` folder, and ``LOGGING_CONF_FILE`` is set accordingly. + +Maia specifics +-------------- + +Maia provides 4 convenience functions that use Maia loggers + +.. code-block:: python + + from maia.utils import logging as mlog + mlog.info('info msg') # uses the 'maia' logger + mlog.stat('stat msg') # uses the 'maia-stats' logger + mlog.warning('warn msg') # uses the 'maia-warnings' logger + mlog.error('error msg') # uses the 'maia-errors' logger diff --git a/docs/1.2/_sources/developer_manual/maia_dev/conventions.rst.txt b/docs/1.2/_sources/developer_manual/maia_dev/conventions.rst.txt new file mode 100644 index 00000000..f171f99a --- /dev/null +++ b/docs/1.2/_sources/developer_manual/maia_dev/conventions.rst.txt @@ -0,0 +1,29 @@ +Conventions +=========== + +Naming conventions +------------------ + +* **snake_case** +* A variable holding a number of things is written :code:`n_thing`. Example: :code:`n_proc`, :code:`n_vtx`. The suffix is singular. +* For unit tests, when testing variable :code:``, the hard-coded expected variable is named :code:`expected_`. +* Usual abbreviations + + * **elt** for **element** + * **vtx** for **vertex** + * **proc** for **process** + * **sz** for **size** (only for local variable names, not functions) + +* Connectivities + + * **cell_vtx** means mesh array of cells described by their vertices (CGNS example: :cgns:`HEXA_8`) + * **cell_face** means the cells described by their faces (CGNS example: :cgns:`NFACE_n`) + * **face_cell** means for each face, the two parent cells (CGNS example: :cgns:`ParentElement`) + * ... so on: **face_vtx**, **edge_vtx**... + * **elt_vtx**, **elt_face**...: in this case, **elt** can be either a cell, a face or an edge + + +Other conventions +----------------- + +We try to use semi-open intervals and 0-indexed structures. This is coherent with Python, C++, and their libraries, but unfortunately not with CGNS. diff --git a/docs/1.2/_sources/developer_manual/maia_dev/development_workflow.rst.txt b/docs/1.2/_sources/developer_manual/maia_dev/development_workflow.rst.txt new file mode 100644 index 00000000..96231c45 --- /dev/null +++ b/docs/1.2/_sources/developer_manual/maia_dev/development_workflow.rst.txt @@ -0,0 +1,34 @@ +Development workflow +==================== + +Sub-modules +----------- + +The **Maia** repository is compatible with the development process described `here `_. It uses git submodules to ease the joint development with other repositories compatible with this organization. + +TL;DR: configure the git repository by sourcing `this file `_ and then execute: + +.. code-block:: bash + + cd $MAIA_FOLDER + git submodule update --init + git_config_submodules + + +Launch tests +------------ + +Tests can be launched by calling CTest, but during the development, we often want to parameterize how to run tests (which ones, number of processes, verbosity level...). + +There is a :code:`source.sh` generated in the :code:`build/` folder. It can be sourced in order to get the correct environment to launch the tests (notably, it updates :code:`LD_LIBRARY_PATH` and :code:`PYTHONPATH` to point to build artifacts). + +Tests can be called with e.g.: + +.. code:: bash + + cd $PROJECT_BUILD_DIR + source source.sh + mpirun -np 4 external/std_e/std_e_unit_tests + ./external/cpp_cgns/cpp_cgns_unit_tests + mpirun -np 4 test/maia_doctest_unit_tests + mpirun -np 4 pytest $PROJECT_SRC_DIR/maia --with-mpi diff --git a/docs/1.2/_sources/index.rst.txt b/docs/1.2/_sources/index.rst.txt new file mode 100644 index 00000000..ee2e8a36 --- /dev/null +++ b/docs/1.2/_sources/index.rst.txt @@ -0,0 +1,42 @@ +**************** +Welcome to Maia! +**************** + +**Maia** is a C++ and Python library for parallel algorithms over CGNS trees: distributed IO, partitioning, data management and various kind of transformations (generation of faces, destructuration, extractions, ...). + +Maia is an open source software developed at `ONERA `_. +Associated source repository and issue tracking are hosted on `Gitlab `_. + +!!!! + +Documentation summary +--------------------- + +:ref:`Quick start ` is the perfect page for a first overview of Maia or to retrieve the environments to use on Onera's clusters. + +:ref:`Introduction ` details the extensions made to the CGNS standard in order to define parallel CGNS trees. + +:ref:`User Manual ` is the main part of this documentation. It describes most of the high level APIs provided by Maia. + +:ref:`Developer Manual ` (under construction) provides more details on some algorithms and can be consulted if you want to contribute to Maia. + + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Reference + + quick_start + installation + introduction/introduction + user_manual/user_manual + developer_manual/developer_manual + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Appendix + + releases/release_notes + related_projects + license diff --git a/docs/1.2/_sources/installation.rst.txt b/docs/1.2/_sources/installation.rst.txt new file mode 100644 index 00000000..d0a8ffcc --- /dev/null +++ b/docs/1.2/_sources/installation.rst.txt @@ -0,0 +1,173 @@ +.. _installation: + +Installation +############ + +Prefered installation procedure +=============================== + +Maia depends on quite a few libraries of different kinds, be it system libraries like MPI, third-party libraries like HDF5, ONERA libraries like ParaDiGM and Cassiopée, git submodules (std_e...), or Python modules (mpi4py, ruamel). The prefered way of installing Maia in a coherent environment is by using the `Spack package manager `_. A Spack recipe for Maia can be found on the `ONERA Spack repository `_. + +Installation through Spack +-------------------------- + +1. Source a Spack repository on your machine. +2. If you don't have a Spack repository ready, you can download one with :code:`git clone https://github.com/spack/spack.git`. On ONERA machines, it is advised to use the `Spacky `_ helper. +3. Download the **ONERA Spack repository** with :code:`git clone https://gitlab.onera.net/informatics/infra/onera_spack_repo.git` +4. Tell Spack that package recipes are in :code:`onera_spack_repo` by adding the following lines to :code:`$SPACK_ROOT/etc/repos.yaml`: + +.. code-block:: yaml + + repos: + - path/to/onera_spack_repo + +(note that **spacky** does steps 3. and 4. for you) + +5. You should be able to see the package options of Maia with :code:`spack info maia` +6. To install Maia: :code:`spack install maia` + + +Development workflow +-------------------- + +For development, it is advised to use Spack to have Maia dependencies, but then follow a typical CMake workflow with :code:`cmake/make`. + + +Dependencies +^^^^^^^^^^^^ + +To get access to Maia dependencies in your development environment, you can: + +* Install a Spack version of Maia, source it in your development environment to get all the dependencies, then override with your own compiled version of Maia +* Do the same, but use a Spack environment containing Maia instead of just the Maia package +* Source a Spack environment where Maia has been removed from the environment view. This can be done by adding the following lines to the :code:`spack.yaml` environement file: + +.. code-block:: yaml + + view: + default: + exclude: ['maia'] + +This last option is cleaner because you are sure that you are not using another version of Maia (but it means you need to create or have access to such an environment view) + +Source the build folder +^^^^^^^^^^^^^^^^^^^^^^^ + +You can develop without the need to install Maia. However, in addition to sourcing your dependencies in your development environment, you also need to source the build artifacts by: + +.. code-block:: bash + + cd $MAIA_BUILD_FOLDER + source source.sh + +The :code:`source.sh` file is created by CMake and will source all Maia artifacts (dynamic libraries, python modules...) + + +Development workflow with submodules +------------------------------------ + +It is often practical to develop Maia with some of its dependencies, namely: + +* project_utils +* std_e +* cpp_cgns +* paradigm +* pytest_parallel + +For that, you need to use git submodules. Maia submodules are located at :code:`$MAIA_FOLDER/external`. To populate them, use :code:`git submodule update --init`. Once done, CMake will use these versions of the dependencies. If you don't populate the submodules, CMake will try to use the ones of your environment (for instance, the one installed by Spack). + +We advise that you use some additional submodule configuration utilities provided in `this file `_. In particular, you should use: + +.. code-block:: bash + + cd $MAIA_FOLDER + git submodule update --init + git_config_submodules + +The detailed meaning of `git_config_submodules` and the git submodule developper workflow of Maia is presented `here `_. + +If you are using Maia submodules, you can filter them out from your Spack environment view like so: + +.. code-block:: yaml + + view: + default: + exclude: ['maia','std-e','cpp-cgns','paradigm','pytest_parallel'] + +Manual installation procedure +============================= + +Dependencies +------------ + +**Maia** depends on: + +* python3 +* MPI +* hdf5 + +* Cassiopée + +* pytest >6 (python package) +* ruamel (python package) +* mpi4py (python package) + +The build process requires: + +* Cmake >= 3.14 +* GCC >= 8 (Clang and Intel should work but no CI) + + +Other dependencies +^^^^^^^^^^^^^^^^^^ + +During the build process, several other libraries will be downloaded: + +* pybind11 +* range-v3 +* doctest + +* ParaDiGM +* project_utils +* std_e +* cpp_cgns + +This process should be transparent. + + +Optional dependencies +^^^^^^^^^^^^^^^^^^^^^ + +The documentation build requires: + +* Doxygen >= 1.8.19 +* Breathe >= 4.15 (python package) +* Sphinx >= 3.00 (python package) + +Build and install +----------------- + +1. Install the required dependencies. They must be in your environment (:code:`PATH`, :code:`LD_LIBRARY_PATH`, :code:`PYTHONPATH`). + + For pytest, you may need these lines : + +.. code:: bash + + pip3 install --user pytest + pip3 install --user pytest-mpi + pip3 install --user pytest-html + pip3 install --user pytest_check + pip3 install --user ruamel.yaml + +2. Then you need to populate your :code:`external` folder. You can do it with :code:`git submodule update --init` + +3. Then use CMake to build maia, e.g. + +.. code:: bash + + SRC_DIR= + BUILD_DIR= + INSTALL_DIR= + cmake -S $SRC_DIR -B$BUILD_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR + cd $BUILD_DIR && make -j && make install + diff --git a/docs/1.2/_sources/introduction/introduction.rst.txt b/docs/1.2/_sources/introduction/introduction.rst.txt new file mode 100644 index 00000000..3392313f --- /dev/null +++ b/docs/1.2/_sources/introduction/introduction.rst.txt @@ -0,0 +1,304 @@ +.. _intro: + +Introduction +============ + +These section introduces the core concepts of distributed and partitioned data +used in Maia, and their application to define parallel CGNS trees. + +Core concepts +------------- + +Dividing data +^^^^^^^^^^^^^ + +:def:`Global data` is the complete data that describes an object. Let's represent it as the +following ordered shapes: + +.. image:: ./images/dist_part/data_full.svg + +Now imagine that you want to split this data into N pieces (N=3 for the next illustrations). +Among all the possibilities, we distinguishes two ways to do it: + +1. Preserving order: we call such repartition :def:`distributed data`, and we use the term :def:`block` + to refer to a piece of this distributed data. + + .. image:: ./images/dist_part/data_dist.svg + + Several distributions are possible, depending on where data is cut, but they all share the same properties: + + - the original order is preserved across the distributed data, + - each element appears in one and only one block, + - a block can be empty as long as the global order is preserved (b). + +2. Taking arbitrary subsets of the original data: we call such subsets :def:`partitioned data`, and we use the term :def:`partition` + to refer to a piece of this partitioned data. + + .. image:: ./images/dist_part/data_part.svg + + Due to this relaxed constraint, there is much more admissible splits since we allow in our definition the following cases: + + - an element can appear in several partitions, or several times within the same partition (b), + - it is allowed that an element does not appear in a partition (c). + + Such repartitions are often useful when trying to gather the elements depending on + some characteristics: on the above example, we created the partition of squared shaped elements, round shaped + elements and unfilled elements (b). Thus, some elements belong to more than one partition. + +A key point is that no *absolute* best way of diving data: depending of what we want to do with the +data, one of the way to divide it may be more adapted. In the previous example: + +- distributed data is fine if you want to count the number of filled shapes: you can count in each + block and then sum the result over the blocks. +- Now assume that you want to renumber the elements depending on their shape, then on their color: + if partitioned data (b) is used, partitions 1 and 2 could independently order + their elements by color since they are already sorted by shape [#f1]_. + +Numberings +^^^^^^^^^^ + +In order to describe the link between our divisions and the original global data, we need to +define additional concepts. + +For distributed data, since the original ordering is respected, the link with the global data is totally implicit: +we just need to know the number of elements in each block or, equivalently, the :def:`distribution array` +of the data. This is an array of size :mono:`N+1` indicating the bounds of each block. By convention, distribution +array starts at 0 and uses semi-open intervals. + + +.. image:: ./images/dist_part/data_dist_gnum.svg + +With this information, the global number of the jth element in the ith block is given by +:math:`\mathtt{dist[i] + j + 1}`. + +On the contrary, for partitioned data, we have to explicitly store the link with the global data: +we use a :def:`local to global numbering array` (often called :mono:`LN_to_GN` for short). +Each partition has its own :mono:`LN_to_GN` array whose size is the number of elements in the partition. + +.. image:: ./images/dist_part/data_part_gnum.svg + +Then, the global number of the jth element in the ith partition is simply given by +:math:`\mathtt{LN\_to\_GN[i][j]}`. + +For any global data, these additional informations allow to create a mapping beetween global data, partitioned +data and distributed data. Thus, it is always possible to reconstruct one of the views from another one. + +Application to MPI parallelism +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The concepts introduced above make all sense in the context of distributed memory computers. +In such architecture, the global data is in fact never detained by a single proc (it would be too heavy): +we always use a distributed or partitioned view of this data. +Using a good repartition is often the key of a well balanced algorithm. + +In the distributed view, we produce as much blocks as the number of MPI processes. Each process holds +its own block, and the distribution array, of size :mono:`n_rank+1`, is know by each process. + +In the partitioned view, we often produce one partition per process; but it can sometime be useful to +put several partitions on a given process. Each process holds the data associated to its partition, +including the related :mono:`LN\_to\_GN` arrays (:mono:`LN\_to\_GN` related to the other partitions +are not know by the current process). + +The :ref:`ParaDiGM ` library provides some low level methods to exchange data between the partitioned and +distributed views in a MPI context, with options to deal with redundant elements, create new distributions, +manage variable strides, etc. + + +Application to meshes +--------------------- + +Until now, we have seen the definition of distributed and partitioned data for a single array +of global data. Let see how to apply these concepts to a basic CFD mesh. + +Consider the following 12-vertices (green numbers), 6-quads (purple numbers) mesh and +assume that it is described by a cell to vertex connectivity. Thus, the mesh file defines +3 global arrays: + +- the CoordinateX and CoordinateY arrays, each one of size 12 +- the Connectivity array of size 6*4 = 24 + +.. image:: ./images/dist_part/full_mesh.svg + +If we have 2 processes at our disposal, a parallel way to load this mesh is to +distribute all the vertex-related entities with a **distribution array** of :code:`[0,6,12]` +and all the element-related entities with a distribution array of :code:`[0,3,6]` [#f2]_: + +.. image:: ./images/dist_part/dist_mesh_arrays.svg + +Then, the blue part of the arrays will be stored on the first process and the red part on the second process. +Looking back at the original mesh, this correspond to the following distributed meshes: + +.. image:: ./images/dist_part/dist_mesh.svg + +with the blue entities stored on the first process, and the red ones on the second process. + + +Notice that the distributed mesh is not suited for solver computation. For instance, cells on the domain treated by the first process are not connex. Worst yet, vertex and element quantities are distributed independently. Take a look at cell 1: it is blue so it belongs to process 0. However,its vertices 9 and 10 are on process 1, this means that there coordinates are not accessible directly on process 0. For these reasons, we want to properly partition our mesh, such as this: + +.. image:: ./images/dist_part/part_mesh.svg + +.. image:: ./images/dist_part/part_mesh_arrays.svg + +Now we have two semi-independent meshes and we can reason about each element with all its associated data +present on the same process. This partitioned view of the mesh has the two following properties: + + - Coherency: every data array is addressable locally, + - Connexity: the data represents geometrical entities that define a local subregion of the mesh. + +We want to keep the link between the base mesh and its partitioned version. For that, we need to store :def:`global numbering arrays`, quantity by quantity: + +.. image:: ./images/dist_part/dist_part_LN_to_GN.svg + +For example, for sub-mesh 0, we can check that element number 3 is actually element number 6 of the original mesh. +We can also see that vertex 3 of sub-mesh 0, and vertex 1 of submesh 1 are actually the same, that is, vertex 4 of the global mesh. + +Thanks to these global numbering, any quantity computed on the partitioned meshes (such as a vertex based field) +can then be transfered to the corresponding distributed vertex, before writting back the results. + +Maia CGNS trees +--------------- + +.. _tree_defs: + +Overview +^^^^^^^^ + +Since Python/CGNS trees describe CFD meshes, we can apply the previous concepts to +define parallel trees. + +A :def:`full tree` is a tree as it is inside a CGNS file, or how it would be loaded by only one process. A full tree is **global data**. + +A :def:`dist tree` is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. See :ref:`dist_tree`. + +A :def:`part tree` is a partial tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. See :ref:`part_tree`. + +A :def:`size tree` is a tree in which only the size of the data is stored. A *size tree* is typically *global data* because each process needs it to know which *block* of data it will have to load and store. + +([Legacy] A :def:`skeleton tree` is a collective tree in which fields and element connectivities are not loaded) + +As explained in previous section, the standard full tree does not make sense in the context of a MPI parallel +application: all the trees encountered within the execution of maia are **distributed trees** or **partitioned trees**. +The next section describe the specification of these trees. + +Specification +^^^^^^^^^^^^^ + +Let us use the following tree as an example: + +.. image:: ./images/trees/tree_seq.png + +This tree is a **global tree**. It may appear like that on a HDF5/CGNS file, or if loaded entirely on one process as a Python/CGNS tree. + +.. _dist_tree: + +Distributed trees +""""""""""""""""" + +A :def:`dist tree` is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. + +If we distribute our tree over two processes, we would then have something like that: + +.. image:: ./images/trees/dist_tree.png + +Let us look at one of them and annotate nodes specific to the distributed tree: + +.. image:: ./images/trees/dist_tree_expl.png + +Arrays of non-constant size are distributed: fields, connectivities, :cgns:`PointLists`. +Others (:cgns:`PointRanges`, :cgns:`CGNSBase_t` and :cgns:`Zone_t` dimensions...) are of limited size and therefore replicated on all processes with virtually no memory penalty. + +On each process, for each entity kind, a **partial distribution** is stored, that gives information of which block of the arrays are stored locally. + +For example, for process 0, the distribution array of vertices of :cgns:`MyZone` is located at :cgns:`MyBase/MyZone/Distribution/Vertex` and is equal to :code:`[0, 9, 18]`. It means that only indices in the semi-open interval :code:`[0 9)` are stored by the **dist tree** on this process, and that the total size of the array is :code:`18`. +This partial distribution applies to arrays spaning all the vertices of the zone, e.g. :cgns:`CoordinateX`. + +More formally, a :def:`partial distribution` related to an entity kind :code:`E` is an array :code:`[start,end,total_size]` of 3 integers where :code:`[start:end)` is a closed/open interval giving, for all global arrays related to :code:`E`, the sub-array that is stored locally on the distributed tree, and :code:`total_size` is the global size of the arrays related to :code:`E`. + +The distributed entities are: + +.. glossary:: + Vertices and Cells + The **partial distribution** are stored in :cgns:`Distribution/Vertex` and :cgns:`Distribution/Cell` nodes at the level of the :cgns:`Zone_t` node. + + Used for example by :cgns:`GridCoordinates_t` and :cgns:`FlowSolution_t` nodes if they do not have a :cgns:`PointList` (i.e. if they span the entire vertices/cells of the zone) + + Quantities described by a :cgns:`PointList` or :cgns:`PointRange` + The **partial distribution** is stored in a :cgns:`Distribution/Index` node at the level of the :cgns:`PointList/PointRange` + + For example, :cgns:`ZoneSubRegion_t` and :cgns:`BCDataSet_t` nodes. + + If the quantity is described by a :cgns:`PointList`, then the :cgns:`PointList` itself is distributed the same way (in contrast, a :cgns:`PointRange` is fully replicated across processes because it is lightweight) + + Connectivities + The **partial distribution** is stored in a :cgns:`Distribution/Element` node at the level of the :cgns:`Element_t` node. Its values are related to the elements, not the vertices of the connectivity array. + + If the element type is heterogenous (NGon, NFace or MIXED) a :cgns:`Distribution/ElementConnectivity` is also present, and this partial distribution is related to the :cgns:`ElementConnectivity` array. + +.. note:: + A distributed tree object is not norm-compliant since most of its arrays are partial: on the previous example, + :cgns:`CoordinateX` array on rank 0 has a length of 9 when :cgns:`MyZone` declares 18 vertices. + However, the union of all the distributed tree objects represents a norm-compliant CGNS tree. + +.. _part_tree: + +Partitioned trees +""""""""""""""""" + +A :def:`part tree` is a partial CGNS tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. + +If we take the global tree from before and partition it, we may get the following tree: + +.. image:: ./images/trees/part_tree.png + +If we annotate the first one: + +.. image:: ./images/trees/part_tree_expl.png + +A **part tree** is just a regular, norm-compliant tree with additional information (in the form of :cgns:`GlobalNumbering` nodes) that keeps the link with the unpartitioned tree it comes from. Notice that the tree structure is **not** necessarily the same across all processes. + +The :cgns:`GlobalNumbering` nodes are located at the same positions that the :cgns:`Distribution` nodes were in the distributed tree. + +A :cgns:`GlobalNumbering` contains information to link an entity in the partition to its corresponding entity in the original tree. For example, the element section :cgns:`Hexa` has a global numbering array of value :code:`[3 4]`. It means: + +* Since it is an array of size 2, there is 2 elements in this section (which is confirmed by the :cgns:`ElementRange`) , +* The first element was the element of id :code:`3` in the original mesh, +* The second element was element :code:`4` in the original mesh. + +Naming conventions +"""""""""""""""""" + +When partitioning, some nodes are split, so there is convention to keep track of the fact they come from the same original node: + +* :cgns:`Zone_t` nodes : :cgns:`MyZone` is split in :cgns:`MyZone.PX.NY` where `X` is the rank of the process, and `Y` is the id of the zone on process `X`. +* Splitable nodes (notably :cgns:`GC_t`) : :cgns:`MyNode` is split in :cgns:`MyNode.N`. They appear in the following scenario: + + * We partition for 3 processes + * :cgns:`Zone0` is connected to :cgns:`Zone1` through :cgns:`GridConnectivity_0_to_1` + * :cgns:`Zone0` is not split (but goes to process 0 and becomes :cgns:`Zone0.P0.N0`). Zone1 is split into :cgns:`Zone1.P1.N0` and :cgns:`Zone1.P2.N0`. Then :cgns:`GridConnectivity_0_to_1` of :cgns:`Zone0` must be split into :cgns:`GridConnectivity_0_to_1.1` and :cgns:`GridConnectivity_0_to_1.2`. + +Note that partitioning may induce new :cgns:`GC_t` internal to the original zone being splitted. Their name is implementation-defined and those nodes do not have a :cgns:`GlobalNumbering` since they did not exist in the original mesh. + +.. _maia_tree: + +Maia trees +^^^^^^^^^^ + +A CGNS tree is said to be a :def:`Maia tree` if it has the following properties: + +* For each unstructured zone, the :cgns:`ElementRange` of all :cgns:`Elements_t` sections + + * are contiguous + * are ordered by ascending dimensions (i.e. edges come first, then faces, then cells) + * the first section starts at 1 + * there is at most one section by element type (e.g. not possible to have two :cgns:`QUAD_4` sections) + +Notice that this is property is required by **some** functions of Maia, not all of them! + +A **Maia tree** may be a **global tree**, a **distributed tree** or a **partitioned tree**. + +.. rubric:: Footnotes + +.. [#f1] Actually, such algorithm could be written for distributed data, but is less intuitive and require more knowledge of what + if happening on the other blocks. + +.. [#f2] Other distributions are possible : we could, for example, affect all the CoordinateX array on the first process (using the distribution array :code:`[0,12,12]`) and the CoordinateY array on the second, but we would have to manage a different distribution for each array. diff --git a/docs/1.2/_sources/license.rst.txt b/docs/1.2/_sources/license.rst.txt new file mode 100644 index 00000000..24330d26 --- /dev/null +++ b/docs/1.2/_sources/license.rst.txt @@ -0,0 +1,390 @@ +.. _license: + +License +======= + +Mozilla Public License Version 2.0 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Definitions +^^^^^^^^^^^^^^ + +**1.1. "Contributor"** + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +**1.2. "Contributor Version"** + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +**1.3. "Contribution"** + means Covered Software of a particular Contributor. + +**1.4. "Covered Software"** + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +**1.5. "Incompatible With Secondary Licenses"** + means + +* **(a)** that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or +* **(b)** that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +**1.6. "Executable Form"** + means any form of the work other than Source Code Form. + +**1.7. "Larger Work"** + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +**1.8. "License"** + means this document. + +**1.9. "Licensable"** + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +**1.10. "Modifications"** + means any of the following: + +* **(a)** any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or +* **(b)** any new file in Source Code Form that contains any Covered + Software. + +**1.11. "Patent Claims" of a Contributor** + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +**1.12. "Secondary License"** + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +**1.13. "Source Code Form"** + means the form of the work preferred for making modifications. + +**1.14. "You" (or "Your")** + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means **(a)** the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or **(b)** ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + + +2. License Grants and Conditions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +2.1. Grants +~~~~~~~~~~~ + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +* **(a)** under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and +* **(b)** under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date +~~~~~~~~~~~~~~~~~~~ + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +* **(a)** for any code that a Contributor has removed from Covered Software; + or +* **(b)** for infringements caused by: **(i)** Your and any other third party's + modifications of Covered Software, or **(ii)** the combination of its + Contributions with other software (except as part of its Contributor + Version); or +* **(c)** under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses +~~~~~~~~~~~~~~~~~~~~~~~~ + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation +~~~~~~~~~~~~~~~~~~~ + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use +~~~~~~~~~~~~~ + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions +~~~~~~~~~~~~~~~ + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + + +3. Responsibilities +^^^^^^^^^^^^^^^^^^^^^^^ + +3.1. Distribution of Source Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If You distribute Covered Software in Executable Form then: + +* **(a)** such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +* **(b)** You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices +~~~~~~~~~~~~ + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + + +4. Inability to Comply Due to Statute or Regulation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: **(a)** comply with +the terms of this License to the maximum extent possible; and **(b)** +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + + +5. Termination +^^^^^^^^^^^^^^ + +**5.1.** The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated **(a)** provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and **(b)** on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +**5.2.** If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + + +6. Disclaimer of Warranty +^^^^^^^^^^^^^^^^^^^^^^^^^ + + Covered Software is provided under this License on an "as is" + basis, without warranty of any kind, either expressed, implied, or + statutory, including, without limitation, warranties that the + Covered Software is free of defects, merchantable, fit for a + particular purpose or non-infringing. The entire risk as to the + quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You + (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an + essential part of this License. No use of any Covered Software is + authorized under this License except under this disclaimer. + +7. Limitation of Liability +^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Under no circumstances and under no legal theory, whether tort + (including negligence), contract, or otherwise, shall any + Contributor, or anyone who distributes Covered Software as + permitted above, be liable to You for any direct, indirect, + special, incidental, or consequential damages of any character + including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any + and all other commercial damages or losses, even if such party + shall have been informed of the possibility of such damages. This + limitation of liability shall not apply to liability for death or + personal injury resulting from such party's negligence to the + extent applicable law prohibits such limitation. Some + jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and + limitation may not apply to You. + + +8. Litigation +^^^^^^^^^^^^^ + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + + +9. Miscellaneous +^^^^^^^^^^^^^^^^ + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + + +10. Versions of the License +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +10.1. New Versions +~~~~~~~~~~~~~~~~~~ + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions +~~~~~~~~~~~~~~~~~~~~~~~ + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary License" Notice +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + + diff --git a/docs/1.2/_sources/quick_start.rst.txt b/docs/1.2/_sources/quick_start.rst.txt new file mode 100644 index 00000000..af0fba63 --- /dev/null +++ b/docs/1.2/_sources/quick_start.rst.txt @@ -0,0 +1,167 @@ +.. _quick_start: + +.. currentmodule:: maia + +Quick start +=========== + +Environnements +-------------- + +Maia is now distributed in elsA releases (since v5.2.01) ! + +If you want to try the latest features, we provide ready-to-go environments including Maia and its dependencies on the following clusters: + +**Spiro-EL8** + +This is the recommended environment for standalone usage of Maia. It works with intel mpi library (2021) +and python version 3.9. + +.. code-block:: sh + + source /scratchm/sonics/dist/source.sh --env maia + module load maia/dev-default + +If you want to use Maia within the standard Spiro environment, the next installation is compatible with +the socle socle-cfd/5.0-intel2120-impi: + +.. code-block:: sh + + module use --append /scratchm/sonics/usr/modules/ + module load maia/dev-dsi-cfd5 + +Note that this is the environment used by elsA for its production spiro-el8_mpi. + +**Sator** + +Similarly, Maia installation are available in both the self maintained and standard socle +on Sator cluster. Sator's version is compiled with support of large integers. + +.. code-block:: sh + + # Versions based on self compiled tools + source /tmp_user/sator/sonics/dist/source.sh --env maia + module load maia/dev-default + + # Versions based on socle-cfd compilers and tools + module use --append /tmp_user/sator/sonics/usr/modules/ + module load maia/dev-dsi-cfd5 + + +If you prefer to build your own version of Maia, see :ref:`installation` section. + +Supported meshes +---------------- + +Maia supports CGNS meshes from version 4.2, meaning that polyhedral connectivities (NGON_n, NFACE_n +and MIXED nodes) must have the ``ElementStartOffset`` node. + +Former meshes can be converted with the (sequential) maia_poly_old_to_new script included +in the ``$PATH`` once the environment is loaded: + +.. code-block:: sh + + $> maia_poly_old_to_new mesh_file.hdf + +The opposite maia_poly_new_to_old script can be used to put back meshes +in old conventions, insuring compatibility with legacy tools. + +.. warning:: CGNS databases should respect the `SIDS `_. + The most commonly observed non-compliant practices are: + + - Empty ``DataArray_t`` (of size 0) under ``FlowSolution_t`` containers. + - 2D shaped (N1,N2) ``DataArray_t`` under ``BCData_t`` containers. + These arrays should be flat (N1xN2,). + - Implicit ``BCDataSet_t`` location for structured meshes: if ``GridLocation_t`` + and ``PointRange_t`` of a given ``BCDataSet_t`` differs from the + parent ``BC_t`` node, theses nodes should be explicitly defined at ``BCDataSet_t`` + level. + + Several non-compliant practices can be detected with the ``cgnscheck`` utility. Do not hesitate + to check your file if Maia is unable to read it. + +Note also that ADF files are not supported; CGNS files should use the HDF binary format. ADF files can +be converted to HDF thanks to ``cgnsconvert``. + +Highlights +---------- + +.. tip:: Download sample files of this section: + :download:`S_twoblocks.cgns <../share/_generated/S_twoblocks.cgns>`, + :download:`U_ATB_45.cgns <../share/_generated/U_ATB_45.cgns>` + +.. rubric:: Daily user-friendly pre & post processing + +Maia provides simple Python APIs to easily setup pre or post processing operations: +for example, converting a structured tree into an unstructured (NGon) tree +is as simple as + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #basic_algo@start + :end-before: #basic_algo@end + :dedent: 2 + +.. image:: ./images/qs_basic.png + :width: 75% + :align: center + +In addition of being parallel, the algorithms are as much as possible topologic, meaning that +they do not rely on a geometric tolerance. This also allow us to preserve the boundary groups +included in the input mesh (colored on the above picture). + +.. rubric:: Building efficient workflows + +By chaining this elementary blocks, you can build a **fully parallel** advanced workflow +running as a **single job** and **minimizing file usage**. + +In the following example, we load an angular section of the +`ATB case `_, +duplicate it to a 180° case, split it, and perform some slices and extractions. + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #workflow@start + :end-before: #workflow@end + :dedent: 2 + +.. image:: ./images/qs_workflow.png + +The above illustration represents the input mesh (gray volumic block) and the +extracted surfacic tree (plane slice and extracted wall BC). Curved lines are +the outline of the volumic mesh after duplication. + +.. rubric:: Compliant with the pyCGNS world + +Finally, since Maia uses the standard `CGNS/Python mapping +`_, +you can set up applications involving multiple python packages: +here, we create and split a mesh with maia, but we then call Cassiopee functions +to compute the gradient of a field on each partition. + +.. literalinclude:: snippets/test_quick_start.py + :start-after: #pycgns@start + :end-before: #pycgns@end + :dedent: 2 + +.. image:: ./images/qs_pycgns.png + +Be aware that other tools often expect to receive geometrically +consistent data, which is why we send the partitioned tree to +Cassiopee functions. The outline of this partitions (using 4 processes) +are materialized by the white lines on the above figure. + + +Resources and Troubleshouting +----------------------------- + +The :ref:`user manual ` describes most of the high level APIs provided by Maia. +If you want to be comfortable with the underlying concepts of distributed and +partitioned trees, have a look at the :ref:`introduction ` section. + +The user manual is illustrated with basic examples. Additional +test cases can be found in +`the sources `_. + +Issues can be reported on +`the gitlab board `_ +and help can also be asked on the dedicated +`Element room `_. diff --git a/docs/1.2/_sources/related_projects.rst.txt b/docs/1.2/_sources/related_projects.rst.txt new file mode 100644 index 00000000..570528b9 --- /dev/null +++ b/docs/1.2/_sources/related_projects.rst.txt @@ -0,0 +1,28 @@ +.. _related: + +################ +Related projects +################ + +CGNS +---- + +The CFD General Notation System (CGNS) provides a general, portable, and extensible standard +for the storage and retrieval of computational fluid dynamics (CFD) analysis data. + +https://cgns.github.io/ + +ParaDiGM +-------- + +The ParaDiGM library provide the developers a progressive framework, which consists of a set of +low-, mid- and high-level services helpfull to write scientific +computing software that rely on a mesh. + +Cassiopée +--------- + +A set of python modules for pre- and post-processing of CFD computations + +http://elsa.onera.fr/Cassiopee/ + diff --git a/docs/1.2/_sources/releases/release_notes.rst.txt b/docs/1.2/_sources/releases/release_notes.rst.txt new file mode 100644 index 00000000..8be29a30 --- /dev/null +++ b/docs/1.2/_sources/releases/release_notes.rst.txt @@ -0,0 +1,80 @@ +.. _release_notes: + +Release notes +============= + +.. _whatsnew: + +.. currentmodule:: maia + +This page contains information about what has changed in each new version of **Maia**. + +v1.2 (July 2023) +---------------- + +💡 New Features +^^^^^^^^^^^^^^^ +- Algo module: add ``adapt_mesh_with_feflo``, wrapping *Feflo.a* to perform mesh adaptation +- Factory module : add ``dist_to_full_tree`` to gather a distributed tree into a standard tree +- File management: add ``read_links`` function to get the links from a CGNS file +- File management: add ``file_to_part_tree`` function to read maia partitioned trees + +🚀 Feature improvements +^^^^^^^^^^^^^^^^^^^^^^^ +- file_to_dist_tree: correct unsigned NFace connectivity if possible +- wall_distance: add an option to take into account periodic connectivities +- poly_old_to_new / poly_new_to_old : support 2D meshes + +🐞 Fixes +^^^^^^^^ +- merge_zones: fix unwanted merge of BCDataSet_t when merge_strategy is None +- partitioning: fix global numbering of S BCDataSet + fix GC-related ZGC +- isosurface: fix poor performances + better management of corner cases +- distributed io: fix read/write of S meshes for data smaller than comm size +- elements to ngon conversion: manage vertex located BCs + +🚧 API change +^^^^^^^^^^^^^ +- redistribute_tree: remove default value for policy +- wall_distance: remove families parameter +- ``distribute_tree`` renamed into ``full_to_dist_tree`` + +🔧 Advanced users / devs +^^^^^^^^^^^^^^^^^^^^^^^^ +- Add a method to give a global id to any object in parallel + +v1.1 (May 2023) +--------------- + +💡 New Features +^^^^^^^^^^^^^^^ + +- Algo module: generate (periodic) 1to1 GridConnectivity between selected BC or GC +- Factory module: generate 2D spherical meshes and points clouds + +🚀 Feature improvements +^^^^^^^^^^^^^^^^^^^^^^^ +- generate_dist_block: enable generation of structured meshes +- partitioning: enable split of 2D (NGON/Elts) and 1D (Elts) meshes +- partitioning: copy AdditionalFamilyName and ReferenceState from BCs to the partitions +- compute_face_center : manage structured meshes +- merge_zones: allow wildcards in zone_paths +- isosurface: recover volumic GCs on surfacic tree (as BCs) +- transfer (part->dist): manage BC/BCDataSet created on partitions for structured meshes + +🐞 Fixes +^^^^^^^^ +- convert_elements_to_ngon: prevent a memory error & better management of 2D meshes +- isosurface: improve robustness of edge reconstruction +- partitioning: fix split of structured GCs and BCDataSet +- merge_zone: fix a bug occurring when FamilyName appears under some BC_t nodes + +🔧 Advanced users / devs +^^^^^^^^^^^^^^^^^^^^^^^^ +- use new pytest_parallel module +- transfer (part->dist): add user callback to reduce shared entities + + +v1.0 (March 2023) +----------------- +First release of Maia ! diff --git a/docs/1.2/_sources/user_manual/algo.rst.txt b/docs/1.2/_sources/user_manual/algo.rst.txt new file mode 100644 index 00000000..64e2a7ff --- /dev/null +++ b/docs/1.2/_sources/user_manual/algo.rst.txt @@ -0,0 +1,110 @@ +Algo module +=========== + +The ``maia.algo`` module provides various algorithms to be applied to one of the +two kind of trees defined by Maia: + +- ``maia.algo.dist`` module contains some operations applying on distributed trees +- ``maia.algo.part`` module contains some operations applying on partitioned trees + +In addition, some algorithms can be applied indistinctly to distributed or partitioned trees. +These algorithms are accessible through the ``maia.algo`` module. + +The ``maia.algo.seq`` module contains a few sequential utility algorithms. + +.. _user_man_dist_algo: + +Distributed algorithms +---------------------- + +The following algorithms applies on maia distributed trees. + + +Connectivities conversions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.convert_s_to_u +.. autofunction:: maia.algo.dist.convert_elements_to_ngon +.. autofunction:: maia.algo.dist.ngons_to_elements +.. autofunction:: maia.algo.dist.convert_elements_to_mixed +.. autofunction:: maia.algo.dist.convert_mixed_to_elements +.. autofunction:: maia.algo.dist.rearrange_element_sections +.. autofunction:: maia.algo.dist.generate_jns_vertex_list + + +Geometry transformations +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.duplicate_from_rotation_jns_to_360 +.. autofunction:: maia.algo.dist.merge_zones +.. autofunction:: maia.algo.dist.merge_zones_from_family +.. autofunction:: maia.algo.dist.merge_connected_zones +.. autofunction:: maia.algo.dist.conformize_jn_pair +.. autofunction:: maia.algo.dist.adapt_mesh_with_feflo + +Interface tools +^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.connect_1to1_families + +Data management +^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.dist.redistribute_tree + +.. + from .extract_surf_dmesh import extract_surf_tree_from_bc + +.. _user_man_part_algo: + +Partitioned algorithms +---------------------- + +The following algorithms applies on maia partitioned trees. + +Geometric calculations +^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.compute_cell_center +.. autofunction:: maia.algo.part.compute_face_center +.. autofunction:: maia.algo.part.compute_edge_center +.. autofunction:: maia.algo.part.compute_wall_distance +.. autofunction:: maia.algo.part.localize_points +.. autofunction:: maia.algo.part.find_closest_points + +Mesh extractions +^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.iso_surface +.. autofunction:: maia.algo.part.plane_slice +.. autofunction:: maia.algo.part.spherical_slice +.. autofunction:: maia.algo.part.extract_part_from_zsr +.. autofunction:: maia.algo.part.extract_part_from_bc_name + +Interpolations +^^^^^^^^^^^^^^ + +.. autofunction:: maia.algo.part.interpolate_from_part_trees +.. autofunction:: maia.algo.part.centers_to_nodes + +.. _user_man_gen_algo: + + +Generic algorithms +------------------ + +The following algorithms applies on maia distributed or partitioned trees + +.. autofunction:: maia.algo.transform_affine +.. autofunction:: maia.algo.pe_to_nface +.. autofunction:: maia.algo.nface_to_pe + + +Sequential algorithms +--------------------- + +The following algorithms applies on regular pytrees. + +.. autofunction:: maia.algo.seq.poly_new_to_old +.. autofunction:: maia.algo.seq.poly_old_to_new +.. autofunction:: maia.algo.seq.enforce_ngon_pe_local diff --git a/docs/1.2/_sources/user_manual/config.rst.txt b/docs/1.2/_sources/user_manual/config.rst.txt new file mode 100644 index 00000000..bb948961 --- /dev/null +++ b/docs/1.2/_sources/user_manual/config.rst.txt @@ -0,0 +1,53 @@ +Configuration +============= + + +Logging +------- + +Maia provides informations to the user through the loggers summarized +in the following table: + ++--------------+-----------------------+---------------------------+ +| Logger | Purpose | Default printer | ++==============+=======================+===========================+ +| maia | Light general info | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-warnings| Warnings | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-errors | Errors | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-stats | More detailed timings | No output | +| | and memory usage | | ++--------------+-----------------------+---------------------------+ + +The easiest way to change this default configuration is to +set the environment variable ``LOGGING_CONF_FILE`` to provide a file +(e.g. logging.conf) looking like this: + +.. code-block:: Text + + maia : mpi_stdout_printer # All ranks output to sdtout + maia-warnings : # No output + maia-errors : mpi_rank_0_stderr_printer # Rank 0 output to stderr + maia-stats : file_printer("stats.log") # All ranks output in the file + +See :ref:`logging` for a full description of available +printers or for instructions on how to create your own printers. +This page also explain how to dynamically change printers +directly in your Python application. + +Exception handling +------------------ + +Maia automatically override the `sys.excepthook +`_ +function to call ``MPI_Abort`` when an uncatched exception occurs. +This allow us to terminate the MPI execution and to avoid some deadlocks +if an exception is raised by a single process. +Note that this call force the global ``COMM_WORLD`` communicator to abort which +can have undesired effects if sub communicators are used. + +This behaviour can be disabled with a call to +``maia.excepthook.disable_mpi_excepthook()``. + diff --git a/doc/user_manual/factory.rst b/docs/1.2/_sources/user_manual/factory.rst.txt similarity index 100% rename from doc/user_manual/factory.rst rename to docs/1.2/_sources/user_manual/factory.rst.txt diff --git a/doc/user_manual/io.rst b/docs/1.2/_sources/user_manual/io.rst.txt similarity index 100% rename from doc/user_manual/io.rst rename to docs/1.2/_sources/user_manual/io.rst.txt diff --git a/docs/1.2/_sources/user_manual/transfer.rst.txt b/docs/1.2/_sources/user_manual/transfer.rst.txt new file mode 100644 index 00000000..4b5d7895 --- /dev/null +++ b/docs/1.2/_sources/user_manual/transfer.rst.txt @@ -0,0 +1,92 @@ +Transfer module +=============== + +The ``maia.transfer`` contains functions that exchange data between the +partitioned and distributed meshes. + +Fields transfer +--------------- + +High level APIs allow to exchange data at CGNS :cgns:`Tree` or :cgns:`Zone` level. +All the provided functions take similar parameters: a distributed tree (resp. zone), +the corresponding partitioned tree (resp. list of zones), the MPI communicator and +optionally a filtering parameter. + +The following kind of data are supported: +:cgns:`FlowSolution_t`, :cgns:`DiscreteData_t`, :cgns:`ZoneSubRegion_t` and :cgns:`BCDataSet_t`. + +When transferring from distributed meshes to partitioned meshes, fields are supposed +to be known on the source mesh across all the ranks (according to :ref:`disttree definition`). +Geometric patches (such as ZoneSubRegion or BCDataSet) must exists on the relevant partitions, meaning +that only fields are transfered. + +When transferring from partitioned meshes to distributed meshes, geometric patches may or may not exist on the +distributed zone: they will be created if needed. This allows to transfer fields that have been created +on the partitioned meshes. +It is however assumed that global data (*e.g.* FlowSolution) are defined on every partition. + +Tree level +^^^^^^^^^^ + +All the functions of this section operate inplace and require the following parameters: + +- **dist_tree** (*CGNSTree*) -- Distributed CGNS Tree +- **part_tree** (*CGNSTree*) -- Corresponding partitioned CGNS Tree +- **comm** (*MPIComm*) -- MPI communicator + +.. autofunction:: maia.transfer.dist_tree_to_part_tree_all +.. autofunction:: maia.transfer.part_tree_to_dist_tree_all + +In addition, the next two methods expect the parameter **labels** (*list of str*) +which allow to pick one or more kind of data to transfer from the supported labels. + +.. autofunction:: maia.transfer.dist_tree_to_part_tree_only_labels +.. autofunction:: maia.transfer.part_tree_to_dist_tree_only_labels + +Zone level +^^^^^^^^^^ + +All the functions of this section operate inplace and require the following parameters: + +- **dist_zone** (*CGNSTree*) -- Distributed CGNS Zone +- **part_zones** (*list of CGNSTree*) -- Corresponding partitioned CGNS Zones +- **comm** (*MPIComm*) -- MPI communicator + +In addition, filtering is possible with the use of the +**include_dict** or **exclude_dict** dictionaries. These dictionaries map +each supported label to a list of cgns paths to include (or exclude). Paths +starts from the ``Zone_t`` node and ends at the targeted ``DataArray_t`` node. +Wildcard ``*`` are allowed in paths : for example, considering the following tree +structure, + +.. code:: + + Zone (Zone_t) + ├── FirstSolution (FlowSolution_t) + │   ├── Pressure (DataArray_t) + │   ├── MomentumX (DataArray_t) + │   └── MomentumY (DataArray_t) + ├── SecondSolution (FlowSolution_t) + │   ├── Pressure (DataArray_t) + │   ├── MomentumX (DataArray_t) + │   └── MomentumY (DataArray_t) + └── SpecialSolution (FlowSolution_t) +    ├── Density (DataArray_t) +    └── MomentumZ (DataArray_t) + +| ``"FirstSolution/Momentum*"`` maps to ``["FirstSolution/MomentumX", "FirstSolution/MomentumY"]``, +| ``"*/Pressure`` maps to ``["FirstSolution/Pressure", "SecondSolution/Pressure"]``, and +| ``"S*/M*"`` maps to ``["SecondSolution/MomentumX", "SecondSolution/MomentumY", "SpecialSolution/MomentumZ"]``. + +For convenience, we also provide the magic path ``['*']`` meaning "everything related to this +label". + +Lastly, we use the following rules to manage missing label keys in dictionaries: + + - For _only functions, we do not transfer any field related to the missing labels; + - For _all functions, we do transfer all the fields related to the missing labels. + +.. autofunction:: maia.transfer.dist_zone_to_part_zones_only +.. autofunction:: maia.transfer.part_zones_to_dist_zone_only +.. autofunction:: maia.transfer.dist_zone_to_part_zones_all +.. autofunction:: maia.transfer.part_zones_to_dist_zone_all diff --git a/docs/1.2/_sources/user_manual/user_manual.rst.txt b/docs/1.2/_sources/user_manual/user_manual.rst.txt new file mode 100644 index 00000000..aedb0917 --- /dev/null +++ b/docs/1.2/_sources/user_manual/user_manual.rst.txt @@ -0,0 +1,49 @@ +.. _user_manual: + +########### +User Manual +########### + +Maia methods are accessible through three main modules : + +- **Factory** allows to generate Maia trees, generally from another kind of tree + (e.g. the partitioning operation). Factory functions return a new tree + whose nature generally differs from the input tree. + +- **Algo** is the main Maia module and provides parallel algorithms to be applied on Maia trees. + Some algorithms are only available for :ref:`distributed trees ` + and some are only available for :ref:`partitioned trees `. + A few algorithms are implemented for both kind + of trees and are thus directly accessible through the :ref:`algo ` module. + + Algo functions either modify their input tree inplace, or return some data, but they do not change the nature + of the tree. + +- **Transfer** is a small module allowing to transfer data between Maia trees. A transfer function operates + on two existing trees and enriches the destination tree with data fields of the source tree. + +Using Maia trees in your application often consists in chaining functions from these different modules. + +.. image:: ./images/workflow.svg + +A typical workflow could be: + +1. Load a structured tree from a file, which produces a **dist tree**. +2. Apply some distributed algorithms to this tree: for example a structured to unstructured conversion (``algo.dist`` module). +3. Generate a corresponding partitionned tree (``factory`` module). +4. Apply some partitioned algorithms to the **part tree**, such as wall distance computation (``algo.part`` module), + and even call you own tools (e.g. a CFD solver) +5. Transfer the resulting fields to the **dist tree** (``transfer`` module). +6. Save the updated dist tree to disk. + +This user manuel describes the main functions available in each module. + +.. toctree:: + :maxdepth: 1 + :hidden: + + config + io + factory + algo + transfer diff --git a/docs/1.2/_static/basic.css b/docs/1.2/_static/basic.css new file mode 100644 index 00000000..bf18350b --- /dev/null +++ b/docs/1.2/_static/basic.css @@ -0,0 +1,906 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/1.2/_static/css/badge_only.css b/docs/1.2/_static/css/badge_only.css new file mode 100644 index 00000000..e380325b --- /dev/null +++ b/docs/1.2/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/1.2/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/1.2/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/1.2/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/1.2/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/1.2/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/1.2/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/1.2/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/1.2/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/1.2/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/1.2/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/1.2/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/1.2/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/1.2/_static/css/fonts/fontawesome-webfont.eot b/docs/1.2/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/docs/1.2/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/1.2/_static/css/fonts/fontawesome-webfont.svg b/docs/1.2/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/docs/1.2/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/1.2/_static/css/fonts/fontawesome-webfont.ttf b/docs/1.2/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/docs/1.2/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/1.2/_static/css/fonts/fontawesome-webfont.woff b/docs/1.2/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/docs/1.2/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/1.2/_static/css/fonts/fontawesome-webfont.woff2 b/docs/1.2/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/docs/1.2/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/1.2/_static/css/fonts/lato-bold-italic.woff b/docs/1.2/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/1.2/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/1.2/_static/css/fonts/lato-bold-italic.woff2 b/docs/1.2/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/1.2/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/1.2/_static/css/fonts/lato-bold.woff b/docs/1.2/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/1.2/_static/css/fonts/lato-bold.woff differ diff --git a/docs/1.2/_static/css/fonts/lato-bold.woff2 b/docs/1.2/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/1.2/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/1.2/_static/css/fonts/lato-normal-italic.woff b/docs/1.2/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/1.2/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/1.2/_static/css/fonts/lato-normal-italic.woff2 b/docs/1.2/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/1.2/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/1.2/_static/css/fonts/lato-normal.woff b/docs/1.2/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/1.2/_static/css/fonts/lato-normal.woff differ diff --git a/docs/1.2/_static/css/fonts/lato-normal.woff2 b/docs/1.2/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/1.2/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/1.2/_static/css/read_the_docs_custom.css b/docs/1.2/_static/css/read_the_docs_custom.css new file mode 100644 index 00000000..f164cfdf --- /dev/null +++ b/docs/1.2/_static/css/read_the_docs_custom.css @@ -0,0 +1,30 @@ +@import 'theme.css'; /* for the Read the Docs theme */ + +.rst-content div[class^="highlight"] pre { + font-size: 95%; +} + +/* +div.indexpage p { + font-size: 5.0em; +} +*/ + +/* override table no-wrap */ +.wy-table-responsive table td, .wy-table-responsive table th { + white-space: normal; +} + +.def { + color: #e41a1c; + font-weight: bold; +} + +.mono { + font-family: 'Courier New', monospace; +} + +.cgns { + color: #08306b; + font-family: 'Courier New', monospace; +} diff --git a/docs/1.2/_static/css/theme.css b/docs/1.2/_static/css/theme.css new file mode 100644 index 00000000..8cd4f101 --- /dev/null +++ b/docs/1.2/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li span.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li span.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li span.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li span.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li span.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p.caption .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.btn .wy-menu-vertical li span.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p.caption .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.nav .wy-menu-vertical li span.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p.caption .btn .headerlink,.rst-content p.caption .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li span.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol li,.rst-content ol.arabic li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content ol.arabic li p:last-child,.rst-content ol.arabic li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover span.toctree-expand,.wy-menu-vertical li.on a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand{display:block;font-size:.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp{user-select:none;pointer-events:none}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink{visibility:hidden;font-size:14px}.rst-content .code-block-caption .headerlink:after,.rst-content .toctree-wrapper>p.caption .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content p.caption .headerlink:after,.rst-content table>caption .headerlink:after{content:"\f0c1";font-family:FontAwesome}.rst-content .code-block-caption:hover .headerlink:after,.rst-content .toctree-wrapper>p.caption:hover .headerlink:after,.rst-content dl dt:hover .headerlink:after,.rst-content h1:hover .headerlink:after,.rst-content h2:hover .headerlink:after,.rst-content h3:hover .headerlink:after,.rst-content h4:hover .headerlink:after,.rst-content h5:hover .headerlink:after,.rst-content h6:hover .headerlink:after,.rst-content p.caption:hover .headerlink:after,.rst-content table>caption:hover .headerlink:after{visibility:visible}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl dt span.classifier:before{content:" : "}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code,html.writer-html4 .rst-content dl:not(.docutils) tt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/1.2/_static/doctools.js b/docs/1.2/_static/doctools.js new file mode 100644 index 00000000..e509e483 --- /dev/null +++ b/docs/1.2/_static/doctools.js @@ -0,0 +1,326 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + var url = new URL(window.location); + url.searchParams.delete('highlight'); + window.history.replaceState({}, '', url); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + break; + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + break; + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/1.2/_static/documentation_options.js b/docs/1.2/_static/documentation_options.js new file mode 100644 index 00000000..2fa8c97f --- /dev/null +++ b/docs/1.2/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/1.2/_static/file.png b/docs/1.2/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/docs/1.2/_static/file.png differ diff --git a/docs/1.2/_static/fonts/Lato/lato-bold.eot b/docs/1.2/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 00000000..3361183a Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/1.2/_static/fonts/Lato/lato-bold.ttf b/docs/1.2/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 00000000..29f691d5 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/1.2/_static/fonts/Lato/lato-bold.woff b/docs/1.2/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/1.2/_static/fonts/Lato/lato-bold.woff2 b/docs/1.2/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/1.2/_static/fonts/Lato/lato-bolditalic.eot b/docs/1.2/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 00000000..3d415493 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/1.2/_static/fonts/Lato/lato-bolditalic.ttf b/docs/1.2/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 00000000..f402040b Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/1.2/_static/fonts/Lato/lato-bolditalic.woff b/docs/1.2/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/1.2/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/1.2/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/1.2/_static/fonts/Lato/lato-italic.eot b/docs/1.2/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 00000000..3f826421 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/1.2/_static/fonts/Lato/lato-italic.ttf b/docs/1.2/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 00000000..b4bfc9b2 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/1.2/_static/fonts/Lato/lato-italic.woff b/docs/1.2/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/1.2/_static/fonts/Lato/lato-italic.woff2 b/docs/1.2/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/1.2/_static/fonts/Lato/lato-regular.eot b/docs/1.2/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 00000000..11e3f2a5 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/1.2/_static/fonts/Lato/lato-regular.ttf b/docs/1.2/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 00000000..74decd9e Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/1.2/_static/fonts/Lato/lato-regular.woff b/docs/1.2/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/1.2/_static/fonts/Lato/lato-regular.woff2 b/docs/1.2/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/1.2/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 00000000..79dc8efe Binary files /dev/null and b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 00000000..df5d1df2 Binary files /dev/null and b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 00000000..2f7ca78a Binary files /dev/null and b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 00000000..eb52a790 Binary files /dev/null and b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/1.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/1.2/_static/graphviz.css b/docs/1.2/_static/graphviz.css new file mode 100644 index 00000000..19e7afd3 --- /dev/null +++ b/docs/1.2/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/docs/1.2/_static/jquery-3.5.1.js b/docs/1.2/_static/jquery-3.5.1.js new file mode 100644 index 00000000..50937333 --- /dev/null +++ b/docs/1.2/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Algorithms description

+

This section provides a detailed description of some algorithms.

+ +
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/developer_manual/algo_description/elements_to_ngons.html b/docs/1.2/developer_manual/algo_description/elements_to_ngons.html new file mode 100644 index 00000000..97b9829b --- /dev/null +++ b/docs/1.2/developer_manual/algo_description/elements_to_ngons.html @@ -0,0 +1,506 @@ + + + + + + + + + + elements_to_ngons — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

elements_to_ngons

+
+

Description

+
maia.algo.dist.elements_to_ngons(dist_tree_elts, comm)
+
+
+

Take a distributed CGNSTree_t or CGNSBase_t, and transform it into a distributed NGon/NFace mesh. The tree is modified in-place.

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD, stable_sort=True)
+
+
+
+
+

Arguments

+
+
dist_tree_elts
+
a distributed tree with
    +
  • unstructured zones that contain regular element sections (i.e. not NGON, not NFACE and not MIXED)

  • +
  • the Maia tree property

  • +
+
+
+
+
comm

a communicator over which elements_to_ngons is called

+
+
+
+
+

Output

+

The tree is modified in-place. The regular element sections are replaced by:

+
    +
  • a NGon section with:

    +
      +
    • An ElementStartOffset/ElementConnectivity node describing:

      +
        +
      • first the external faces in exactly the same order as they were (in particular, gathered by face type)

      • +
      • then the internal faces, also gathered by face type

      • +
      +
    • +
    • a ParentElements node and a ParentElementsPosition node

    • +
    +
  • +
  • a NFace section with cells ordered as they were, with ids only shifted by the number of interior faces

  • +
+
+
+

Parallelism

+

elements_to_ngons is a collective function.

+
+
+

Complexity

+

With \(N\) the number of zones, \(n_i\) the number of elements of zone \(i\), \(n_{f,i}\) its number of interior faces, and \(K\) the number of processes

+
+
Sequential time

\(\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i \cdot log(n_i) \right)\)

+

The cost is dominated by the sorting of faces (interior and exterior) used to spot identical ones.

+
+
Parallel time

\(\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i/K \cdot log(n_i) \right)\)

+

The parallel distributed sort algorithm consists of three steps:

+
+
    +
  1. A partitioning step that locally gather faces into \(K\) buckets that are of equal global size. This uses a \(K\)-generalized variant of quickselect that is of complexity \(\displaystyle \mathcal{O} \left( n_i/K \cdot log(K) \right)\)

  2. +
  3. An all_to_all exchange step that gather the \(K\) buckets on the \(K\) processes. This step is not accounted for here (see below)

  4. +
  5. A local sorting step that is \(\displaystyle \mathcal{O} \left( n_i/K \cdot log(n_i/K) \right)\)

  6. +
+
+

If we sum up step 1 and 3, we get

+
+
+
+\[\begin{split}\begin{equation} \label{eq1} +\begin{split} + \textrm{C}_\textrm{parallel_sort} & = \mathcal{O} \left( n_i/K \cdot log(K) + n_i/K \cdot log(n_i/K) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot \left( log(K) + log(n_i/K) \right) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot log(n_i) \right) +\end{split} +\end{equation}\end{split}\]
+
+
Theoretical scaling

\(\textrm{Speedup} = K\)

+

Experimentally, the scaling is much worse - under investigation.

+

Note: the speedup is computed by \(\textrm{Speedup} = t_s / t_p\) where \(t_s\) is the sequential time and \(t_p\) the parallel time. A speedup of \(K\) is perfect, a speedup lower than \(1\) means that sequential execution is faster.

+
+
Peak memory

Approximately \(\displaystyle \sum_{i=0}^N 2 n_i + n_{f,i}\)

+

This is the size of the input tree + the output tree. \(n_i\) is counted twice: once for the input element connectivity, once for the output NFace connectivity

+
+
Size of communications

Approximately \(\displaystyle \sum_{i=0}^N 3 n_{f,i} + n_i\) all_to_all calls

+

For each zone, one all_to_all call to sort interior faces, one to send back the faces to the NFace, one to concatenate all faces, one to concatenate all cells

+
+
Number of communication calls

Should be \(\displaystyle \mathcal{O} \left( \sum_{i=0}^N log(n_i/K) \right)\)

+

The number of communications is constant, except for the algorithm finding a balanced distribution of interior faces

+
+
Note

In practice, \(n_{f,i}\) varies from \(2 n_i\) (tet-dominant meshes) to \(3 n_i\) (hex-dominant meshes).

+
+
+
+
+

Algorithm explanation

+
maia::elements_to_ngons(cgns::tree& dist_zone, MPI_Comm comm)
+
+
+

The algorithm is divided in two steps:

+
    +
  1. Generate interior faces, insert them between exterior faces and cells. During the process, also compute the parent information (ParentElements and ParentElementsPosition) and add the CellFace connectivity to cells. All elements keep their standard element types, in particular interior faces are inserted by section type (e.g. there will be a TRI_3_interior node and a QUAD_4_interior node, not a NGon node)

  2. +
  3. Transform all sections into NGon/NFace

  4. +
+
+

Generate interior faces

+
+

Simplified algorithm

+

Let us look at a simplified sequential algorithm first:

+
1. For all element sections (3D and 2D):
+  Generate faces
+    => FaceVtx arrays (one for Tris, one for Quads)
+    => Associated Parent and ParentPosition arrays
+        (only one parent by element at this stage)
+  Interior faces are generated twice (by their two parent cells)
+  Exterior faces are generated twice (by a parent cell and a boundary face)
+
+2. For each element kind in {Tri,Quad}
+  Sort the FaceVtx array
+    (ordering: lexicographical comparison of vertices)
+  Sort the parent arrays accordingly
+    => now each face appears consecutively exactly twice
+        for interior faces,
+          the FaceVtx is always inverted between the two occurences
+        for exterior faces,
+          it depends on the normal convention
+
+Note that interior and exterior faces can be distinguised
+  by looking at the id of their parents
+
+3. For each interior face appearing twice:
+  Create an interior face section where:
+    the first FaceVtx is kept
+    the two parents are stored
+4. For each exterior face appearing twice:
+  One of the face is the original boundary section face,
+    one was generated from the joint cell
+  Send back to the original boundary face its parent face id and position
+    => store the parent information of the boundary face
+
+
+
+
+

Parallel algorithm

+

The algorithm is very similar to the sequential one. We need to modify two operations:

+
+
Sorting of the FaceVtx array (step 2)

The parallel sorting is done in three steps:

+
    +
  1. apply a partial sort std_e::sort_by_rank that will determine the rank of each FaceVtx

  2. +
  3. call an all_to_all communication step that sends each connectivity to its rank, based on the information of the previous step

  4. +
  5. sort each received FaceVtx locally

  6. +
+
+
Send back boundary parents and position to the original boundary faces (step 4)

Since the original face can be remote, this is a parallel communication operation using std_e::scatter

+
+
+
+
+

Computation of the CellFace

+

After step 2, we have all the faces exactly once, with their parent ids and parent positions. +We can then compute the CellFace of each cell section by the following algorithm:

+
For each cell section:
+  pre-allocate the CellFace array
+    (its size is n_cell_in_section * n_face_of_cell_type)
+  view it as a global distributed array
+For each unique face:
+  For each of its parent cells (could be one or two):
+    send the parent cell the id of the face and its position
+    insert the result in the CellFace array
+
+
+

As previously, the send operation uses a scatter pattern

+
+
+
+

Transform all sections into NGon/NFace

+

Thanks to the previous algorithm, we have:

+
    +
  • all exterior and interior faces with their parent information

  • +
  • the CellFace connectivity of the cell sections

  • +
+

Elements are ordered in something akin to this:

+
    +
  • boundary tris

  • +
  • boundary quads

  • +
  • internal tris

  • +
  • internal quads

  • +
  • tetras

  • +
  • pyras

  • +
  • prisms

  • +
  • hexas

  • +
+

The algorithm then just needs to:

+
    +
  • concatenate all FaceVtx of the faces into a NGon node and add a ElementStartOffset

  • +
  • concatenate all CellFace of the cells into a NFace node and add a ElementStartOffset

  • +
+
+
+
+

Design alternatives

+

The final step of the computation involves concatenating faces and cells global section by global section. This requires two heavyweight all_to_all calls. An alternative would be to concatenate locally. This would imply two trade-offs:

+
    +
  • the faces and cells would then not be globally gathered by type, and the exterior faces would not be first

  • +
  • all the PointLists (including those where GridLocation=FaceCenter) would have to be shifted

  • +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/developer_manual/developer_manual.html b/docs/1.2/developer_manual/developer_manual.html new file mode 100644 index 00000000..ffddeb70 --- /dev/null +++ b/docs/1.2/developer_manual/developer_manual.html @@ -0,0 +1,274 @@ + + + + + + + + + + Developer Manual — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/developer_manual/logging.html b/docs/1.2/developer_manual/logging.html new file mode 100644 index 00000000..20be032f --- /dev/null +++ b/docs/1.2/developer_manual/logging.html @@ -0,0 +1,378 @@ + + + + + + + + + + Log management — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Log management

+
+

Loggers

+

A logger is a global object where an application or a library can log to. +It can be declared with

+
from maia.utils.logging import add_logger
+
+add_logger("my_logger")
+
+
+

or with the equivalent C++ code

+
#include "std_e/logging/log.hpp"
+
+std_e::add_logger("my_logger");
+
+
+

A logger declared in Python is available in C++ and vice-versa: we do not need to create it twice.

+
+

Note

+

Loggers are available to pure C/C++ programs (in particular, test programs that do not use the Python interpreter).

+
+

It can then be referred to by its name. If we want to log a string to my_logger, we will do it like so:

+
from maia.utils.logging import log
+
+log('my_logger', 'my message')
+
+
+
#include "std_e/logging/log.hpp"
+
+std_e::log("my_logger", "my message");
+
+
+

Typically, an application will use several loggers to log messages by themes. By convention, the name of a logger in the application my_app should begin with my_app. For instance, loggers can be named: my_app, my_app-stats, my_app-errors

+

Loggers are both

+
    +
  • developper-oriented, that is, when a developper wants to output a message, he should select a logger (existing or not) encompassing the theme of the message he wants to log,

  • +
  • user-oriented, because a user can choose what logger he wants to listen to.

  • +
+
+
+

Printers

+

By itself, a logger does not do anything with the messages it receives. For that, we need to attach printers to a logger that will handle its messages.

+

For instance, we can attach a printer that will output the message to the console:

+
from maia.utils.logging import add_printer_to_logger
+add_printer_to_logger('my_logger','stdout_printer')
+
+
+

Printers are user-oriented: it is the user of the application who decides what he wants to do with the message that each logger is receiving, by attaching none or several printers to a logger.

+
+

Available printers

+
+
stdout_printer, stderr_printer

output messages to the console (respectively stdout and stderr)

+
+
mpi_stdout_printer, mpi_stderr_printer

output messages to the console, but prefix them by MPI.COMM_WORLD.Get_rank()

+
+
mpi_rank_0_stdout_printer, mpi_rank_0_stderr_printer

output messages to the console, if MPI.COMM_WORLD.Get_rank()==0

+
+
file_printer(‘my_file.extension’)

output messages to file my_file.extension

+
+
mpi_file_printer(‘my_file.extension’)

output messages to files my_file.{rk}.extension, with rk = MPI.COMM_WORLD.Get_rank()

+
+
+
+

Note

+

MPI-aware printers use MPI.COMM_WORLD rather than a local communicator. While the latter would be more correct, it would imply local instanciation of printers, at the time or after a new communicator is created. While this is certainly doable, up until now we feel that it does not worth the burden. But if the need arises, they can still be added.

+
+
+
+

Create your own printer

+

Any Python type can be used as a printer as long as it provides a log method that accepts a string argument.

+
from maia.utils.logging import add_printer_to_logger
+
+class my_printer:
+  def log(self, msg):
+    print(msg)
+
+add_printer_to_logger('my_logger',my_printer())
+
+
+
+
+
+

Configuration file

+

Loggers are associated to default printers. While they can be configured anytime in the Python scripts, most of the time, reading a configuration file at the start of the program is enough. The program will try to read a configuration file if the environment variable LOGGING_CONF_FILE is set. A logging configuration file looks like this:

+
my_app : mpi_stdout_printer
+my_app-my_theme : mpi_file_printer('my_theme.log')
+
+
+

For developpers, a logging file logging.conf with loggers and default printers is put in the build/ folder, and LOGGING_CONF_FILE is set accordingly.

+
+
+

Maia specifics

+

Maia provides 4 convenience functions that use Maia loggers

+
from maia.utils import logging as mlog
+mlog.info('info msg') # uses the 'maia' logger
+mlog.stat('stat msg') # uses the 'maia-stats' logger
+mlog.warning('warn msg') # uses the 'maia-warnings' logger
+mlog.error('error msg') # uses the 'maia-errors' logger
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/developer_manual/maia_dev/conventions.html b/docs/1.2/developer_manual/maia_dev/conventions.html new file mode 100644 index 00000000..c524dfe9 --- /dev/null +++ b/docs/1.2/developer_manual/maia_dev/conventions.html @@ -0,0 +1,301 @@ + + + + + + + + + + Conventions — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Conventions

+
+

Naming conventions

+
    +
  • snake_case

  • +
  • A variable holding a number of things is written n_thing. Example: n_proc, n_vtx. The suffix is singular.

  • +
  • For unit tests, when testing variable <var>, the hard-coded expected variable is named expected_<var>.

  • +
  • Usual abbreviations

    +
      +
    • elt for element

    • +
    • vtx for vertex

    • +
    • proc for process

    • +
    • sz for size (only for local variable names, not functions)

    • +
    +
  • +
  • Connectivities

    +
      +
    • cell_vtx means mesh array of cells described by their vertices (CGNS example: HEXA_8)

    • +
    • cell_face means the cells described by their faces (CGNS example: NFACE_n)

    • +
    • face_cell means for each face, the two parent cells (CGNS example: ParentElement)

    • +
    • … so on: face_vtx, edge_vtx

    • +
    • elt_vtx, elt_face…: in this case, elt can be either a cell, a face or an edge

    • +
    +
  • +
+
+
+

Other conventions

+

We try to use semi-open intervals and 0-indexed structures. This is coherent with Python, C++, and their libraries, but unfortunately not with CGNS.

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/developer_manual/maia_dev/development_workflow.html b/docs/1.2/developer_manual/maia_dev/development_workflow.html new file mode 100644 index 00000000..1874a97d --- /dev/null +++ b/docs/1.2/developer_manual/maia_dev/development_workflow.html @@ -0,0 +1,296 @@ + + + + + + + + + + Development workflow — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Development workflow

+
+

Sub-modules

+

The Maia repository is compatible with the development process described here. It uses git submodules to ease the joint development with other repositories compatible with this organization.

+

TL;DR: configure the git repository by sourcing this file and then execute:

+
cd $MAIA_FOLDER
+git submodule update --init
+git_config_submodules
+
+
+
+
+

Launch tests

+

Tests can be launched by calling CTest, but during the development, we often want to parameterize how to run tests (which ones, number of processes, verbosity level…).

+

There is a source.sh generated in the build/ folder. It can be sourced in order to get the correct environment to launch the tests (notably, it updates LD_LIBRARY_PATH and PYTHONPATH to point to build artifacts).

+

Tests can be called with e.g.:

+
cd $PROJECT_BUILD_DIR
+source source.sh
+mpirun -np 4 external/std_e/std_e_unit_tests
+./external/cpp_cgns/cpp_cgns_unit_tests
+mpirun -np 4 test/maia_doctest_unit_tests
+mpirun -np 4 pytest $PROJECT_SRC_DIR/maia --with-mpi
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/genindex.html b/docs/1.2/genindex.html new file mode 100644 index 00000000..9b2b4370 --- /dev/null +++ b/docs/1.2/genindex.html @@ -0,0 +1,541 @@ + + + + + + + + + + Index — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Index
  • + + +
  • + + + +
  • + +
+ + +
+
+
+
+ + +

Index

+ +
+ A + | C + | D + | E + | F + | G + | I + | L + | M + | N + | P + | Q + | R + | S + | T + | V + | W + | Z + +
+

A

+ + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

I

+ + + +
+ +

L

+ + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

P

+ + + +
+ +

Q

+ + +
+ +

R

+ + + +
+ +

S

+ + +
+ +

T

+ + +
+ +

V

+ + +
+ +

W

+ + + +
+ +

Z

+ + +
+ + + +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/index.html b/docs/1.2/index.html new file mode 100644 index 00000000..fd2ecf56 --- /dev/null +++ b/docs/1.2/index.html @@ -0,0 +1,273 @@ + + + + + + + + + + Welcome to Maia! — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Welcome to Maia!

+

Maia is a C++ and Python library for parallel algorithms over CGNS trees: distributed IO, partitioning, data management and various kind of transformations (generation of faces, destructuration, extractions, …).

+

Maia is an open source software developed at ONERA. +Associated source repository and issue tracking are hosted on Gitlab.

+
+
+

Documentation summary

+

Quick start is the perfect page for a first overview of Maia or to retrieve the environments to use on Onera’s clusters.

+

Introduction details the extensions made to the CGNS standard in order to define parallel CGNS trees.

+

User Manual is the main part of this documentation. It describes most of the high level APIs provided by Maia.

+

Developer Manual (under construction) provides more details on some algorithms and can be consulted if you want to contribute to Maia.

+
+
+
+
+
+
+ + +
+ +
+
+ + +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/installation.html b/docs/1.2/installation.html new file mode 100644 index 00000000..fc4f199b --- /dev/null +++ b/docs/1.2/installation.html @@ -0,0 +1,427 @@ + + + + + + + + + + Installation — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Installation

+
+

Prefered installation procedure

+

Maia depends on quite a few libraries of different kinds, be it system libraries like MPI, third-party libraries like HDF5, ONERA libraries like ParaDiGM and Cassiopée, git submodules (std_e…), or Python modules (mpi4py, ruamel). The prefered way of installing Maia in a coherent environment is by using the Spack package manager. A Spack recipe for Maia can be found on the ONERA Spack repository.

+
+

Installation through Spack

+
    +
  1. Source a Spack repository on your machine.

  2. +
  3. If you don’t have a Spack repository ready, you can download one with git clone https://github.com/spack/spack.git. On ONERA machines, it is advised to use the Spacky helper.

  4. +
  5. Download the ONERA Spack repository with git clone https://gitlab.onera.net/informatics/infra/onera_spack_repo.git

  6. +
  7. Tell Spack that package recipes are in onera_spack_repo by adding the following lines to $SPACK_ROOT/etc/repos.yaml:

  8. +
+
repos:
+- path/to/onera_spack_repo
+
+
+

(note that spacky does steps 3. and 4. for you)

+
    +
  1. You should be able to see the package options of Maia with spack info maia

  2. +
  3. To install Maia: spack install maia

  4. +
+
+
+

Development workflow

+

For development, it is advised to use Spack to have Maia dependencies, but then follow a typical CMake workflow with cmake/make.

+
+

Dependencies

+

To get access to Maia dependencies in your development environment, you can:

+
    +
  • Install a Spack version of Maia, source it in your development environment to get all the dependencies, then override with your own compiled version of Maia

  • +
  • Do the same, but use a Spack environment containing Maia instead of just the Maia package

  • +
  • Source a Spack environment where Maia has been removed from the environment view. This can be done by adding the following lines to the spack.yaml environement file:

  • +
+
view:
+  default:
+    exclude: ['maia']
+
+
+

This last option is cleaner because you are sure that you are not using another version of Maia (but it means you need to create or have access to such an environment view)

+
+
+

Source the build folder

+

You can develop without the need to install Maia. However, in addition to sourcing your dependencies in your development environment, you also need to source the build artifacts by:

+
cd $MAIA_BUILD_FOLDER
+source source.sh
+
+
+

The source.sh file is created by CMake and will source all Maia artifacts (dynamic libraries, python modules…)

+
+
+
+

Development workflow with submodules

+

It is often practical to develop Maia with some of its dependencies, namely:

+
    +
  • project_utils

  • +
  • std_e

  • +
  • cpp_cgns

  • +
  • paradigm

  • +
  • pytest_parallel

  • +
+

For that, you need to use git submodules. Maia submodules are located at $MAIA_FOLDER/external. To populate them, use git submodule update --init. Once done, CMake will use these versions of the dependencies. If you don’t populate the submodules, CMake will try to use the ones of your environment (for instance, the one installed by Spack).

+

We advise that you use some additional submodule configuration utilities provided in this file. In particular, you should use:

+
cd $MAIA_FOLDER
+git submodule update --init
+git_config_submodules
+
+
+

The detailed meaning of git_config_submodules and the git submodule developper workflow of Maia is presented here.

+

If you are using Maia submodules, you can filter them out from your Spack environment view like so:

+
view:
+  default:
+    exclude: ['maia','std-e','cpp-cgns','paradigm','pytest_parallel']
+
+
+
+
+
+

Manual installation procedure

+
+

Dependencies

+

Maia depends on:

+
    +
  • python3

  • +
  • MPI

  • +
  • hdf5

  • +
  • Cassiopée

  • +
  • pytest >6 (python package)

  • +
  • ruamel (python package)

  • +
  • mpi4py (python package)

  • +
+

The build process requires:

+
    +
  • Cmake >= 3.14

  • +
  • GCC >= 8 (Clang and Intel should work but no CI)

  • +
+
+

Other dependencies

+

During the build process, several other libraries will be downloaded:

+
    +
  • pybind11

  • +
  • range-v3

  • +
  • doctest

  • +
  • ParaDiGM

  • +
  • project_utils

  • +
  • std_e

  • +
  • cpp_cgns

  • +
+

This process should be transparent.

+
+
+

Optional dependencies

+

The documentation build requires:

+
    +
  • Doxygen >= 1.8.19

  • +
  • Breathe >= 4.15 (python package)

  • +
  • Sphinx >= 3.00 (python package)

  • +
+
+
+
+

Build and install

+
    +
  1. Install the required dependencies. They must be in your environment (PATH, LD_LIBRARY_PATH, PYTHONPATH).

  2. +
+
+

For pytest, you may need these lines :

+
+
pip3 install --user pytest
+pip3 install --user pytest-mpi
+pip3 install --user pytest-html
+pip3 install --user pytest_check
+pip3 install --user ruamel.yaml
+
+
+
    +
  1. Then you need to populate your external folder. You can do it with git submodule update --init

  2. +
  3. Then use CMake to build maia, e.g.

  4. +
+
SRC_DIR=<path to source repo>
+BUILD_DIR=<path to tmp build dir>
+INSTALL_DIR=<path to where you want to install Maia>
+cmake -S $SRC_DIR -B$BUILD_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR
+cd $BUILD_DIR && make -j && make install
+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/introduction/introduction.html b/docs/1.2/introduction/introduction.html new file mode 100644 index 00000000..879b3cc7 --- /dev/null +++ b/docs/1.2/introduction/introduction.html @@ -0,0 +1,510 @@ + + + + + + + + + + Introduction — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Introduction

+

These section introduces the core concepts of distributed and partitioned data +used in Maia, and their application to define parallel CGNS trees.

+
+

Core concepts

+
+

Dividing data

+

Global data is the complete data that describes an object. Let’s represent it as the +following ordered shapes:

+../_images/data_full.svg

Now imagine that you want to split this data into N pieces (N=3 for the next illustrations). +Among all the possibilities, we distinguishes two ways to do it:

+
    +
  1. Preserving order: we call such repartition distributed data, and we use the term block +to refer to a piece of this distributed data.

  2. +
+
+
../_images/data_dist.svg

Several distributions are possible, depending on where data is cut, but they all share the same properties:

+
+
    +
  • the original order is preserved across the distributed data,

  • +
  • each element appears in one and only one block,

  • +
  • a block can be empty as long as the global order is preserved (b).

  • +
+
+
+
    +
  1. Taking arbitrary subsets of the original data: we call such subsets partitioned data, and we use the term partition +to refer to a piece of this partitioned data.

  2. +
+
+
../_images/data_part.svg

Due to this relaxed constraint, there is much more admissible splits since we allow in our definition the following cases:

+
+
    +
  • an element can appear in several partitions, or several times within the same partition (b),

  • +
  • it is allowed that an element does not appear in a partition (c).

  • +
+
+

Such repartitions are often useful when trying to gather the elements depending on +some characteristics: on the above example, we created the partition of squared shaped elements, round shaped +elements and unfilled elements (b). Thus, some elements belong to more than one partition.

+
+

A key point is that no absolute best way of diving data: depending of what we want to do with the +data, one of the way to divide it may be more adapted. In the previous example:

+
    +
  • distributed data is fine if you want to count the number of filled shapes: you can count in each +block and then sum the result over the blocks.

  • +
  • Now assume that you want to renumber the elements depending on their shape, then on their color: +if partitioned data (b) is used, partitions 1 and 2 could independently order +their elements by color since they are already sorted by shape 1.

  • +
+
+
+

Numberings

+

In order to describe the link between our divisions and the original global data, we need to +define additional concepts.

+

For distributed data, since the original ordering is respected, the link with the global data is totally implicit: +we just need to know the number of elements in each block or, equivalently, the distribution array +of the data. This is an array of size N+1 indicating the bounds of each block. By convention, distribution +array starts at 0 and uses semi-open intervals.

+../_images/data_dist_gnum.svg

With this information, the global number of the jth element in the ith block is given by +\(\mathtt{dist[i] + j + 1}\).

+

On the contrary, for partitioned data, we have to explicitly store the link with the global data: +we use a local to global numbering array (often called LN_to_GN for short). +Each partition has its own LN_to_GN array whose size is the number of elements in the partition.

+../_images/data_part_gnum.svg

Then, the global number of the jth element in the ith partition is simply given by +\(\mathtt{LN\_to\_GN[i][j]}\).

+

For any global data, these additional informations allow to create a mapping beetween global data, partitioned +data and distributed data. Thus, it is always possible to reconstruct one of the views from another one.

+
+
+

Application to MPI parallelism

+

The concepts introduced above make all sense in the context of distributed memory computers. +In such architecture, the global data is in fact never detained by a single proc (it would be too heavy): +we always use a distributed or partitioned view of this data. +Using a good repartition is often the key of a well balanced algorithm.

+

In the distributed view, we produce as much blocks as the number of MPI processes. Each process holds +its own block, and the distribution array, of size n_rank+1, is know by each process.

+

In the partitioned view, we often produce one partition per process; but it can sometime be useful to +put several partitions on a given process. Each process holds the data associated to its partition, +including the related LN_to_GN arrays (LN_to_GN related to the other partitions +are not know by the current process).

+

The ParaDiGM library provides some low level methods to exchange data between the partitioned and +distributed views in a MPI context, with options to deal with redundant elements, create new distributions, +manage variable strides, etc.

+
+
+
+

Application to meshes

+

Until now, we have seen the definition of distributed and partitioned data for a single array +of global data. Let see how to apply these concepts to a basic CFD mesh.

+

Consider the following 12-vertices (green numbers), 6-quads (purple numbers) mesh and +assume that it is described by a cell to vertex connectivity. Thus, the mesh file defines +3 global arrays:

+
    +
  • the CoordinateX and CoordinateY arrays, each one of size 12

  • +
  • the Connectivity array of size 6*4 = 24

  • +
+../_images/full_mesh.svg

If we have 2 processes at our disposal, a parallel way to load this mesh is to +distribute all the vertex-related entities with a distribution array of [0,6,12] +and all the element-related entities with a distribution array of [0,3,6] 2:

+../_images/dist_mesh_arrays.svg

Then, the blue part of the arrays will be stored on the first process and the red part on the second process. +Looking back at the original mesh, this correspond to the following distributed meshes:

+../_images/dist_mesh.svg

with the blue entities stored on the first process, and the red ones on the second process.

+

Notice that the distributed mesh is not suited for solver computation. For instance, cells on the domain treated by the first process are not connex. Worst yet, vertex and element quantities are distributed independently. Take a look at cell 1: it is blue so it belongs to process 0. However,its vertices 9 and 10 are on process 1, this means that there coordinates are not accessible directly on process 0. For these reasons, we want to properly partition our mesh, such as this:

+../_images/part_mesh.svg../_images/part_mesh_arrays.svg

Now we have two semi-independent meshes and we can reason about each element with all its associated data +present on the same process. This partitioned view of the mesh has the two following properties:

+
+
    +
  • Coherency: every data array is addressable locally,

  • +
  • Connexity: the data represents geometrical entities that define a local subregion of the mesh.

  • +
+
+

We want to keep the link between the base mesh and its partitioned version. For that, we need to store global numbering arrays, quantity by quantity:

+../_images/dist_part_LN_to_GN.svg

For example, for sub-mesh 0, we can check that element number 3 is actually element number 6 of the original mesh. +We can also see that vertex 3 of sub-mesh 0, and vertex 1 of submesh 1 are actually the same, that is, vertex 4 of the global mesh.

+

Thanks to these global numbering, any quantity computed on the partitioned meshes (such as a vertex based field) +can then be transfered to the corresponding distributed vertex, before writting back the results.

+
+
+

Maia CGNS trees

+
+

Overview

+

Since Python/CGNS trees describe CFD meshes, we can apply the previous concepts to +define parallel trees.

+

A full tree is a tree as it is inside a CGNS file, or how it would be loaded by only one process. A full tree is global data.

+

A dist tree is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. See Distributed trees.

+

A part tree is a partial tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. See Partitioned trees.

+

A size tree is a tree in which only the size of the data is stored. A size tree is typically global data because each process needs it to know which block of data it will have to load and store.

+

([Legacy] A skeleton tree is a collective tree in which fields and element connectivities are not loaded)

+

As explained in previous section, the standard full tree does not make sense in the context of a MPI parallel +application: all the trees encountered within the execution of maia are distributed trees or partitioned trees. +The next section describe the specification of these trees.

+
+
+

Specification

+

Let us use the following tree as an example:

+../_images/tree_seq.png +

This tree is a global tree. It may appear like that on a HDF5/CGNS file, or if loaded entirely on one process as a Python/CGNS tree.

+
+

Distributed trees

+

A dist tree is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array.

+

If we distribute our tree over two processes, we would then have something like that:

+../_images/dist_tree.png +

Let us look at one of them and annotate nodes specific to the distributed tree:

+../_images/dist_tree_expl.png +

Arrays of non-constant size are distributed: fields, connectivities, PointLists. +Others (PointRanges, CGNSBase_t and Zone_t dimensions…) are of limited size and therefore replicated on all processes with virtually no memory penalty.

+

On each process, for each entity kind, a partial distribution is stored, that gives information of which block of the arrays are stored locally.

+

For example, for process 0, the distribution array of vertices of MyZone is located at MyBase/MyZone/Distribution/Vertex and is equal to [0, 9, 18]. It means that only indices in the semi-open interval [0 9) are stored by the dist tree on this process, and that the total size of the array is 18. +This partial distribution applies to arrays spaning all the vertices of the zone, e.g. CoordinateX.

+

More formally, a partial distribution related to an entity kind E is an array [start,end,total_size] of 3 integers where [start:end) is a closed/open interval giving, for all global arrays related to E, the sub-array that is stored locally on the distributed tree, and total_size is the global size of the arrays related to E.

+

The distributed entities are:

+
+
Vertices and Cells

The partial distribution are stored in Distribution/Vertex and Distribution/Cell nodes at the level of the Zone_t node.

+

Used for example by GridCoordinates_t and FlowSolution_t nodes if they do not have a PointList (i.e. if they span the entire vertices/cells of the zone)

+
+
Quantities described by a PointList or PointRange

The partial distribution is stored in a Distribution/Index node at the level of the PointList/PointRange

+

For example, ZoneSubRegion_t and BCDataSet_t nodes.

+

If the quantity is described by a PointList, then the PointList itself is distributed the same way (in contrast, a PointRange is fully replicated across processes because it is lightweight)

+
+
Connectivities

The partial distribution is stored in a Distribution/Element node at the level of the Element_t node. Its values are related to the elements, not the vertices of the connectivity array.

+

If the element type is heterogenous (NGon, NFace or MIXED) a Distribution/ElementConnectivity is also present, and this partial distribution is related to the ElementConnectivity array.

+
+
+
+

Note

+

A distributed tree object is not norm-compliant since most of its arrays are partial: on the previous example, +CoordinateX array on rank 0 has a length of 9 when MyZone declares 18 vertices. +However, the union of all the distributed tree objects represents a norm-compliant CGNS tree.

+
+
+
+

Partitioned trees

+

A part tree is a partial CGNS tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process.

+

If we take the global tree from before and partition it, we may get the following tree:

+../_images/part_tree.png +

If we annotate the first one:

+../_images/part_tree_expl.png +

A part tree is just a regular, norm-compliant tree with additional information (in the form of GlobalNumbering nodes) that keeps the link with the unpartitioned tree it comes from. Notice that the tree structure is not necessarily the same across all processes.

+

The GlobalNumbering nodes are located at the same positions that the Distribution nodes were in the distributed tree.

+

A GlobalNumbering contains information to link an entity in the partition to its corresponding entity in the original tree. For example, the element section Hexa has a global numbering array of value [3 4]. It means:

+
    +
  • Since it is an array of size 2, there is 2 elements in this section (which is confirmed by the ElementRange) ,

  • +
  • The first element was the element of id 3 in the original mesh,

  • +
  • The second element was element 4 in the original mesh.

  • +
+
+
+

Naming conventions

+

When partitioning, some nodes are split, so there is convention to keep track of the fact they come from the same original node:

+
    +
  • Zone_t nodes : MyZone is split in MyZone.PX.NY where X is the rank of the process, and Y is the id of the zone on process X.

  • +
  • Splitable nodes (notably GC_t) : MyNode is split in MyNode.N. They appear in the following scenario:

    +
      +
    • We partition for 3 processes

    • +
    • Zone0 is connected to Zone1 through GridConnectivity_0_to_1

    • +
    • Zone0 is not split (but goes to process 0 and becomes Zone0.P0.N0). Zone1 is split into Zone1.P1.N0 and Zone1.P2.N0. Then GridConnectivity_0_to_1 of Zone0 must be split into GridConnectivity_0_to_1.1 and GridConnectivity_0_to_1.2.

    • +
    +
  • +
+

Note that partitioning may induce new GC_t internal to the original zone being splitted. Their name is implementation-defined and those nodes do not have a GlobalNumbering since they did not exist in the original mesh.

+
+
+
+

Maia trees

+

A CGNS tree is said to be a Maia tree if it has the following properties:

+
    +
  • For each unstructured zone, the ElementRange of all Elements_t sections

    +
      +
    • are contiguous

    • +
    • are ordered by ascending dimensions (i.e. edges come first, then faces, then cells)

    • +
    • the first section starts at 1

    • +
    • there is at most one section by element type (e.g. not possible to have two QUAD_4 sections)

    • +
    +
  • +
+

Notice that this is property is required by some functions of Maia, not all of them!

+

A Maia tree may be a global tree, a distributed tree or a partitioned tree.

+

Footnotes

+
+
1
+

Actually, such algorithm could be written for distributed data, but is less intuitive and require more knowledge of what +if happening on the other blocks.

+
+
2
+

Other distributions are possible : we could, for example, affect all the CoordinateX array on the first process (using the distribution array [0,12,12]) and the CoordinateY array on the second, but we would have to manage a different distribution for each array.

+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/license.html b/docs/1.2/license.html new file mode 100644 index 00000000..de481ddd --- /dev/null +++ b/docs/1.2/license.html @@ -0,0 +1,679 @@ + + + + + + + + + + License — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

License

+
+

Mozilla Public License Version 2.0

+
+
+

1. Definitions

+
+
1.1. “Contributor”

means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software.

+
+
1.2. “Contributor Version”

means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor’s Contribution.

+
+
1.3. “Contribution”

means Covered Software of a particular Contributor.

+
+
1.4. “Covered Software”

means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof.

+
+
1.5. “Incompatible With Secondary Licenses”

means

+
+
+
    +
  • +
    (a) that the initial Contributor has attached the notice described

    in Exhibit B to the Covered Software; or

    +
    +
    +
  • +
  • +
    (b) that the Covered Software was made available under the terms of

    version 1.1 or earlier of the License, but not also under the +terms of a Secondary License.

    +
    +
    +
  • +
+
+
1.6. “Executable Form”

means any form of the work other than Source Code Form.

+
+
1.7. “Larger Work”

means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software.

+
+
1.8. “License”

means this document.

+
+
1.9. “Licensable”

means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License.

+
+
1.10. “Modifications”

means any of the following:

+
+
+
    +
  • +
    (a) any file in Source Code Form that results from an addition to,

    deletion from, or modification of the contents of Covered +Software; or

    +
    +
    +
  • +
  • +
    (b) any new file in Source Code Form that contains any Covered

    Software.

    +
    +
    +
  • +
+
+
1.11. “Patent Claims” of a Contributor

means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version.

+
+
1.12. “Secondary License”

means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses.

+
+
1.13. “Source Code Form”

means the form of the work preferred for making modifications.

+
+
1.14. “You” (or “Your”)

means an individual or a legal entity exercising rights under this +License. For legal entities, “You” includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, “control” means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity.

+
+
+
+
+

2. License Grants and Conditions

+
+

2.1. Grants

+

Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license:

+
    +
  • +
    (a) under intellectual property rights (other than patent or trademark)

    Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and

    +
    +
    +
  • +
  • +
    (b) under Patent Claims of such Contributor to make, use, sell, offer

    for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version.

    +
    +
    +
  • +
+
+
+

2.2. Effective Date

+

The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution.

+
+
+

2.3. Limitations on Grant Scope

+

The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor:

+
    +
  • +
    (a) for any code that a Contributor has removed from Covered Software;

    or

    +
    +
    +
  • +
  • +
    (b) for infringements caused by: (i) Your and any other third party’s

    modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or

    +
    +
    +
  • +
  • +
    (c) under Patent Claims infringed by Covered Software in the absence of

    its Contributions.

    +
    +
    +
  • +
+

This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4).

+
+
+

2.4. Subsequent Licenses

+

No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3).

+
+
+

2.5. Representation

+

Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License.

+
+
+

2.6. Fair Use

+

This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents.

+
+
+

2.7. Conditions

+

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1.

+
+
+
+

3. Responsibilities

+
+

3.1. Distribution of Source Form

+

All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients’ rights in the Source Code +Form.

+
+
+

3.2. Distribution of Executable Form

+

If You distribute Covered Software in Executable Form then:

+
    +
  • +
    (a) such Covered Software must also be made available in Source Code

    Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and

    +
    +
    +
  • +
  • +
    (b) You may distribute such Executable Form under the terms of this

    License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients’ rights in the Source Code Form under this License.

    +
    +
    +
  • +
+
+
+

3.3. Distribution of a Larger Work

+

You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s).

+
+
+

3.4. Notices

+

You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies.

+
+
+

3.5. Application of Additional Terms

+

You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction.

+
+
+
+

4. Inability to Comply Due to Statute or Regulation

+

If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it.

+
+
+

5. Termination

+

5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice.

+

5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate.

+

5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination.

+
+
+

6. Disclaimer of Warranty

+
+

Covered Software is provided under this License on an “as is” +basis, without warranty of any kind, either expressed, implied, or +statutory, including, without limitation, warranties that the +Covered Software is free of defects, merchantable, fit for a +particular purpose or non-infringing. The entire risk as to the +quality and performance of the Covered Software is with You. +Should any Covered Software prove defective in any respect, You +(not any Contributor) assume the cost of any necessary servicing, +repair, or correction. This disclaimer of warranty constitutes an +essential part of this License. No use of any Covered Software is +authorized under this License except under this disclaimer.

+
+
+
+

7. Limitation of Liability

+
+

Under no circumstances and under no legal theory, whether tort +(including negligence), contract, or otherwise, shall any +Contributor, or anyone who distributes Covered Software as +permitted above, be liable to You for any direct, indirect, +special, incidental, or consequential damages of any character +including, without limitation, damages for lost profits, loss of +goodwill, work stoppage, computer failure or malfunction, or any +and all other commercial damages or losses, even if such party +shall have been informed of the possibility of such damages. This +limitation of liability shall not apply to liability for death or +personal injury resulting from such party’s negligence to the +extent applicable law prohibits such limitation. Some +jurisdictions do not allow the exclusion or limitation of +incidental or consequential damages, so this exclusion and +limitation may not apply to You.

+
+
+
+

8. Litigation

+

Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party’s ability to bring +cross-claims or counter-claims.

+
+
+

9. Miscellaneous

+

This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor.

+
+
+

10. Versions of the License

+
+

10.1. New Versions

+

Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number.

+
+
+

10.2. Effect of New Versions

+

You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward.

+
+
+

10.3. Modified Versions

+

If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License).

+
+
+

10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses

+

If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached.

+
+
+
+

Exhibit A - Source Code Form License Notice

+
+

This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/.

+
+

If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice.

+

You may add additional accurate notices of copyright ownership.

+
+
+

Exhibit B - “Incompatible With Secondary License” Notice

+
+

This Source Code Form is “Incompatible With Secondary Licenses”, as +defined by the Mozilla Public License, v. 2.0.

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/objects.inv b/docs/1.2/objects.inv new file mode 100644 index 00000000..34c18207 Binary files /dev/null and b/docs/1.2/objects.inv differ diff --git a/docs/1.2/quick_start.html b/docs/1.2/quick_start.html new file mode 100644 index 00000000..c3da06bd --- /dev/null +++ b/docs/1.2/quick_start.html @@ -0,0 +1,437 @@ + + + + + + + + + + Quick start — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Quick start

+
+

Environnements

+

Maia is now distributed in elsA releases (since v5.2.01) !

+

If you want to try the latest features, we provide ready-to-go environments including Maia and its dependencies on the following clusters:

+

Spiro-EL8

+

This is the recommended environment for standalone usage of Maia. It works with intel mpi library (2021) +and python version 3.9.

+
source /scratchm/sonics/dist/source.sh --env maia
+module load maia/dev-default
+
+
+

If you want to use Maia within the standard Spiro environment, the next installation is compatible with +the socle socle-cfd/5.0-intel2120-impi:

+
module use --append /scratchm/sonics/usr/modules/
+module load maia/dev-dsi-cfd5
+
+
+

Note that this is the environment used by elsA for its production spiro-el8_mpi.

+

Sator

+

Similarly, Maia installation are available in both the self maintained and standard socle +on Sator cluster. Sator’s version is compiled with support of large integers.

+
# Versions based on self compiled tools
+source /tmp_user/sator/sonics/dist/source.sh --env maia
+module load maia/dev-default
+
+# Versions based on socle-cfd compilers and tools
+module use --append /tmp_user/sator/sonics/usr/modules/
+module load maia/dev-dsi-cfd5
+
+
+

If you prefer to build your own version of Maia, see Installation section.

+
+
+

Supported meshes

+

Maia supports CGNS meshes from version 4.2, meaning that polyhedral connectivities (NGON_n, NFACE_n +and MIXED nodes) must have the ElementStartOffset node.

+

Former meshes can be converted with the (sequential) maia_poly_old_to_new script included +in the $PATH once the environment is loaded:

+
$> maia_poly_old_to_new mesh_file.hdf
+
+
+

The opposite maia_poly_new_to_old script can be used to put back meshes +in old conventions, insuring compatibility with legacy tools.

+
+

Warning

+

CGNS databases should respect the SIDS. +The most commonly observed non-compliant practices are:

+
    +
  • Empty DataArray_t (of size 0) under FlowSolution_t containers.

  • +
  • 2D shaped (N1,N2) DataArray_t under BCData_t containers. +These arrays should be flat (N1xN2,).

  • +
  • Implicit BCDataSet_t location for structured meshes: if GridLocation_t +and PointRange_t of a given BCDataSet_t differs from the +parent BC_t node, theses nodes should be explicitly defined at BCDataSet_t +level.

  • +
+

Several non-compliant practices can be detected with the cgnscheck utility. Do not hesitate +to check your file if Maia is unable to read it.

+
+

Note also that ADF files are not supported; CGNS files should use the HDF binary format. ADF files can +be converted to HDF thanks to cgnsconvert.

+
+
+

Highlights

+
+

Tip

+

Download sample files of this section: +S_twoblocks.cgns, +U_ATB_45.cgns

+
+

Daily user-friendly pre & post processing

+

Maia provides simple Python APIs to easily setup pre or post processing operations: +for example, converting a structured tree into an unstructured (NGon) tree +is as simple as

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia
+
+tree_s = maia.io.file_to_dist_tree('S_twoblocks.cgns', comm)
+tree_u = maia.algo.dist.convert_s_to_ngon(tree_s, comm)
+maia.io.dist_tree_to_file(tree_u, 'U_twoblocks.cgns', comm)
+
+
+_images/qs_basic.png +

In addition of being parallel, the algorithms are as much as possible topologic, meaning that +they do not rely on a geometric tolerance. This also allow us to preserve the boundary groups +included in the input mesh (colored on the above picture).

+

Building efficient workflows

+

By chaining this elementary blocks, you can build a fully parallel advanced workflow +running as a single job and minimizing file usage.

+

In the following example, we load an angular section of the +ATB case, +duplicate it to a 180° case, split it, and perform some slices and extractions.

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia.pytree as PT
+import maia
+
+# Read the file. Tree is distributed
+dist_tree = maia.io.file_to_dist_tree('U_ATB_45.cgns', comm)
+
+# Duplicate the section to a 180° mesh
+# and merge all the blocks into one
+opposite_jns = [['Base/bump_45/ZoneGridConnectivity/matchA'],
+                ['Base/bump_45/ZoneGridConnectivity/matchB']]
+maia.algo.dist.duplicate_from_periodic_jns(dist_tree,
+    ['Base/bump_45'], opposite_jns, 22, comm)
+maia.algo.dist.merge_connected_zones(dist_tree, comm)
+
+# Split the mesh to have a partitioned tree
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Now we can call some partitioned algorithms
+maia.algo.part.compute_wall_distance(part_tree, comm, point_cloud='Vertex')
+extract_tree = maia.algo.part.extract_part_from_bc_name(part_tree, "wall", comm)
+slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0], comm,
+      containers_name=['WallDistance'])
+
+# Merge extractions in a same tree in order to save it
+base = PT.get_child_from_label(slice_tree, 'CGNSBase_t')
+PT.set_name(base, f'PlaneSlice')
+PT.add_child(extract_tree, base)
+maia.algo.pe_to_nface(dist_tree,comm)
+
+extract_tree_dist = maia.factory.recover_dist_tree(extract_tree, comm)
+maia.io.dist_tree_to_file(extract_tree_dist, 'ATB_extract.cgns', comm)
+
+
+_images/qs_workflow.png +

The above illustration represents the input mesh (gray volumic block) and the +extracted surfacic tree (plane slice and extracted wall BC). Curved lines are +the outline of the volumic mesh after duplication.

+

Compliant with the pyCGNS world

+

Finally, since Maia uses the standard CGNS/Python mapping, +you can set up applications involving multiple python packages: +here, we create and split a mesh with maia, but we then call Cassiopee functions +to compute the gradient of a field on each partition.

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia
+import Transform.PyTree as CTransform
+import Converter.PyTree as CConverter
+import Post.PyTree      as CPost
+
+dist_tree = maia.factory.generate_dist_block([101,6,6], 'TETRA_4', comm)
+CTransform._scale(dist_tree, [5,1,1], X=(0,0,0))
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+CConverter._initVars(part_tree, '{Field}=sin({nodes:CoordinateX})')
+part_tree = CPost.computeGrad(part_tree, 'Field')
+
+maia.transfer.part_tree_to_dist_tree_all(dist_tree, part_tree, comm)
+maia.io.dist_tree_to_file(dist_tree, 'out.cgns', comm)
+
+
+_images/qs_pycgns.png +

Be aware that other tools often expect to receive geometrically +consistent data, which is why we send the partitioned tree to +Cassiopee functions. The outline of this partitions (using 4 processes) +are materialized by the white lines on the above figure.

+
+
+

Resources and Troubleshouting

+

The user manual describes most of the high level APIs provided by Maia. +If you want to be comfortable with the underlying concepts of distributed and +partitioned trees, have a look at the introduction section.

+

The user manual is illustrated with basic examples. Additional +test cases can be found in +the sources.

+

Issues can be reported on +the gitlab board +and help can also be asked on the dedicated +Element room.

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/related_projects.html b/docs/1.2/related_projects.html new file mode 100644 index 00000000..a4b4a287 --- /dev/null +++ b/docs/1.2/related_projects.html @@ -0,0 +1,282 @@ + + + + + + + + + + Related projects — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/releases/release_notes.html b/docs/1.2/releases/release_notes.html new file mode 100644 index 00000000..e752a1a8 --- /dev/null +++ b/docs/1.2/releases/release_notes.html @@ -0,0 +1,365 @@ + + + + + + + + + + Release notes — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Release notes

+

This page contains information about what has changed in each new version of Maia.

+
+

v1.2 (July 2023)

+
+

💡 New Features

+
    +
  • Algo module: add adapt_mesh_with_feflo, wrapping Feflo.a to perform mesh adaptation

  • +
  • Factory module : add dist_to_full_tree to gather a distributed tree into a standard tree

  • +
  • File management: add read_links function to get the links from a CGNS file

  • +
  • File management: add file_to_part_tree function to read maia partitioned trees

  • +
+
+
+

🚀 Feature improvements

+
    +
  • file_to_dist_tree: correct unsigned NFace connectivity if possible

  • +
  • wall_distance: add an option to take into account periodic connectivities

  • +
  • poly_old_to_new / poly_new_to_old : support 2D meshes

  • +
+
+
+

🐞 Fixes

+
    +
  • merge_zones: fix unwanted merge of BCDataSet_t when merge_strategy is None

  • +
  • partitioning: fix global numbering of S BCDataSet + fix GC-related ZGC

  • +
  • isosurface: fix poor performances + better management of corner cases

  • +
  • distributed io: fix read/write of S meshes for data smaller than comm size

  • +
  • elements to ngon conversion: manage vertex located BCs

  • +
+
+
+

🚧 API change

+
    +
  • redistribute_tree: remove default value for policy

  • +
  • wall_distance: remove families parameter

  • +
  • distribute_tree renamed into full_to_dist_tree

  • +
+
+
+

🔧 Advanced users / devs

+
    +
  • Add a method to give a global id to any object in parallel

  • +
+
+
+
+

v1.1 (May 2023)

+
+

💡 New Features

+
    +
  • Algo module: generate (periodic) 1to1 GridConnectivity between selected BC or GC

  • +
  • Factory module: generate 2D spherical meshes and points clouds

  • +
+
+
+

🚀 Feature improvements

+
    +
  • generate_dist_block: enable generation of structured meshes

  • +
  • partitioning: enable split of 2D (NGON/Elts) and 1D (Elts) meshes

  • +
  • partitioning: copy AdditionalFamilyName and ReferenceState from BCs to the partitions

  • +
  • compute_face_center : manage structured meshes

  • +
  • merge_zones: allow wildcards in zone_paths

  • +
  • isosurface: recover volumic GCs on surfacic tree (as BCs)

  • +
  • transfer (part->dist): manage BC/BCDataSet created on partitions for structured meshes

  • +
+
+
+

🐞 Fixes

+
    +
  • convert_elements_to_ngon: prevent a memory error & better management of 2D meshes

  • +
  • isosurface: improve robustness of edge reconstruction

  • +
  • partitioning: fix split of structured GCs and BCDataSet

  • +
  • merge_zone: fix a bug occurring when FamilyName appears under some BC_t nodes

  • +
+
+
+

🔧 Advanced users / devs

+
    +
  • use new pytest_parallel module

  • +
  • transfer (part->dist): add user callback to reduce shared entities

  • +
+
+
+
+

v1.0 (March 2023)

+

First release of Maia !

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/search.html b/docs/1.2/search.html new file mode 100644 index 00000000..380461d3 --- /dev/null +++ b/docs/1.2/search.html @@ -0,0 +1,268 @@ + + + + + + + + + + Search — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Search
  • + + +
  • + +
  • + +
+ + +
+
+
+
+ + + + +
+ +
+ +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/searchindex.js b/docs/1.2/searchindex.js new file mode 100644 index 00000000..fd94d96d --- /dev/null +++ b/docs/1.2/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["developer_manual/algo_description","developer_manual/algo_description/elements_to_ngons","developer_manual/developer_manual","developer_manual/logging","developer_manual/maia_dev/conventions","developer_manual/maia_dev/development_workflow","index","installation","introduction/introduction","license","quick_start","related_projects","releases/release_notes","user_manual/algo","user_manual/config","user_manual/factory","user_manual/io","user_manual/transfer","user_manual/user_manual"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["developer_manual/algo_description.rst","developer_manual/algo_description/elements_to_ngons.rst","developer_manual/developer_manual.rst","developer_manual/logging.rst","developer_manual/maia_dev/conventions.rst","developer_manual/maia_dev/development_workflow.rst","index.rst","installation.rst","introduction/introduction.rst","license.rst","quick_start.rst","related_projects.rst","releases/release_notes.rst","user_manual/algo.rst","user_manual/config.rst","user_manual/factory.rst","user_manual/io.rst","user_manual/transfer.rst","user_manual/user_manual.rst"],objects:{"":[[15,0,1,"","dump_pdm_output"],[15,0,1,"","graph_part_tool"],[15,0,1,"","part_interface_loc"],[15,0,1,"","preserve_orientation"],[15,0,1,"","reordering"],[15,0,1,"","zone_to_parts"]],"maia.algo":[[13,1,1,"","nface_to_pe"],[13,1,1,"","pe_to_nface"],[13,1,1,"","transform_affine"]],"maia.algo.dist":[[13,1,1,"","adapt_mesh_with_feflo"],[13,1,1,"","conformize_jn_pair"],[13,1,1,"","connect_1to1_families"],[13,1,1,"","convert_elements_to_mixed"],[13,1,1,"","convert_elements_to_ngon"],[13,1,1,"","convert_mixed_to_elements"],[13,1,1,"","convert_s_to_u"],[13,1,1,"","duplicate_from_rotation_jns_to_360"],[13,1,1,"","generate_jns_vertex_list"],[13,1,1,"","merge_connected_zones"],[13,1,1,"","merge_zones"],[13,1,1,"","merge_zones_from_family"],[13,1,1,"","ngons_to_elements"],[13,1,1,"","rearrange_element_sections"],[13,1,1,"","redistribute_tree"]],"maia.algo.part":[[13,1,1,"","centers_to_nodes"],[13,1,1,"","compute_cell_center"],[13,1,1,"","compute_edge_center"],[13,1,1,"","compute_face_center"],[13,1,1,"","compute_wall_distance"],[13,1,1,"","extract_part_from_bc_name"],[13,1,1,"","extract_part_from_zsr"],[13,1,1,"","find_closest_points"],[13,1,1,"","interpolate_from_part_trees"],[13,1,1,"","iso_surface"],[13,1,1,"","localize_points"],[13,1,1,"","plane_slice"],[13,1,1,"","spherical_slice"]],"maia.algo.seq":[[13,1,1,"","enforce_ngon_pe_local"],[13,1,1,"","poly_new_to_old"],[13,1,1,"","poly_old_to_new"]],"maia.factory":[[15,1,1,"","dist_to_full_tree"],[15,1,1,"","full_to_dist_tree"],[15,1,1,"","generate_dist_block"],[15,1,1,"","generate_dist_points"],[15,1,1,"","generate_dist_sphere"],[15,1,1,"","partition_dist_tree"],[15,1,1,"","recover_dist_tree"]],"maia.factory.partitioning":[[15,1,1,"","compute_balanced_weights"],[15,1,1,"","compute_nosplit_weights"],[15,1,1,"","compute_regular_weights"]],"maia.io":[[16,1,1,"","dist_tree_to_file"],[16,1,1,"","file_to_dist_tree"],[16,1,1,"","file_to_part_tree"],[16,1,1,"","part_tree_to_file"],[16,1,1,"","read_links"],[16,1,1,"","read_tree"],[16,1,1,"","write_tree"],[16,1,1,"","write_trees"]],"maia.transfer":[[17,1,1,"","dist_tree_to_part_tree_all"],[17,1,1,"","dist_tree_to_part_tree_only_labels"],[17,1,1,"","dist_zone_to_part_zones_all"],[17,1,1,"","dist_zone_to_part_zones_only"],[17,1,1,"","part_tree_to_dist_tree_all"],[17,1,1,"","part_tree_to_dist_tree_only_labels"],[17,1,1,"","part_zones_to_dist_zone_all"],[17,1,1,"","part_zones_to_dist_zone_only"]]},objnames:{"0":["py","attribute","Python attribute"],"1":["py","function","Python function"]},objtypes:{"0":"py:attribute","1":"py:function"},terms:{"0":[1,3,4,8,10,13,14,15,16,17],"00":7,"01":10,"1":[1,7,8,10,13,15],"10":[8,13,15,16],"100":13,"101":10,"10m":15,"11":[9,13],"12":[8,9],"13":9,"14":[7,9,13],"15":7,"170":13,"18":8,"180":[10,13],"19":7,"1d":12,"1e":13,"1to1":[12,13],"2":[1,8,10,13,15],"20":[13,15],"2021":10,"21":13,"22":10,"24":8,"25":13,"2d":[1,10,12,13,15],"3":[1,7,8,10,13,15],"30":9,"375":15,"3d":[1,13,15],"4":[1,3,5,7,8,10,13],"45":13,"5":[10,13,15],"50":9,"6":[7,8,10,13],"60":9,"625":15,"8":7,"9":[8,10],"case":[4,8,9,10,12,13,15,16],"cassiop\u00e9":7,"class":[3,15],"default":[3,7,10,12,13,14,15,16],"do":[3,7,8,9,10,16,17,18],"final":[1,9,10],"float":[13,15],"function":[1,3,4,8,10,12,13,14,15,16,17,18],"import":[1,3,9,10,13,15,16,17],"int":15,"long":[3,8],"new":[3,8,13,18],"return":[13,15,16,18],"short":8,"true":[1,13,15,16],"try":[3,4,7,8,10],"var":4,"while":3,A:[1,3,4,7,8,11,13,18],As:[1,8],Be:[10,16],But:3,By:[3,8,10],For:[1,3,4,7,8,9,13,15,16,17],If:[1,3,7,8,9,10,13,15,16],In:[1,7,8,9,10,13,15,16,17],It:[3,5,6,7,8,10,13,17],Its:8,No:[9,14],On:[7,8],One:1,Such:[8,9],The:[1,3,4,5,7,8,9,10,11,13,14,15,16,17],Their:8,Then:[7,8],There:5,These:[8,10,13,17],To:7,With:[1,8],_:1,_all:17,_exchange_field:13,_gn:8,_initvar:10,_onli:17,_scale:10,_to:8,_unmatch:13,abbrevi:4,abil:9,abl:[7,9],abort:14,about:[8,12],abov:[8,9,10,13,15,16],absenc:9,absolut:[8,9,15],accept:[3,13,16],access:[7,8,13,18],accord:[13,15,17],accordingli:[1,3,13],account:[1,12,13],accur:9,achiev:16,across:[8,15,17],action:9,actual:[8,13],ad:[3,7,13,16],adapt:[8,12,13],adapt_mesh_with_feflo:[12,13],add:[1,9,12,13],add_child:10,add_logg:3,add_printer_to_logg:3,addit:[7,8,10,13,16,17],addition:9,additionalfamilynam:[12,13],address:8,adf:10,admiss:[8,13,15],admit:13,adpt_dist_tre:13,advanc:10,advic:13,advis:7,affect:[8,9,15,16],affero:9,affin:13,after:[1,3,9,10,13],against:9,agre:9,agreement:9,akin:1,algo:[1,10,12,18],algorithm:[2,6,8,10,15,18],all:[7,8,9,10,13,14,15,16,17],all_to_al:1,alleg:9,alloc:1,allow:[8,9,10,12,13,14,17,18],almost:15,alon:9,alreadi:8,also:[1,7,8,9,10,13,14,16,17],alter:9,altern:15,although:13,alwai:[1,8,13],among:8,an:[1,3,4,6,7,8,9,10,12,13,14,15],analysi:11,angl:13,angular:[10,13],ani:[3,8,9,12,13,17],annot:8,anoth:[7,8,18],anyon:9,anyth:3,anytim:3,api:[6,10,13,17],apparatu:9,appear:[1,8,12,13,16],append:10,appli:[1,8,9,13,18],applic:[3,10,14,18],apply_to_field:13,approxim:1,ar:[1,3,6,7,8,9,10,13,14,15,16,17,18],arang:13,arbitrari:8,architectur:8,argument:[3,13,15],aris:3,arithmet:13,arrai:[1,4,8,10,13,15],artifact:[5,7],ascend:[8,13],ask:10,assert:[9,13,15,17],associ:[1,3,6,8,15],assum:[8,9,17],atb:10,atb_extract:10,attach:[3,9],attempt:9,author:9,automat:[9,14,15,16],avail:[9,10,13,14,15,16,18],averag:13,avoid:14,awar:[3,10,16],ax:13,b:[7,8,13],back:[1,8,9,10],balanc:[1,8,13,15],bar:15,base:[1,8,10,13,15],basea:13,baseb:13,basenam:13,basi:9,basic:[8,10,13,15],bc:[10,12,13,16],bc_name:13,bc_t:[10,12,13],bcdata_t:10,bcdataset:[12,13,15,17],bcdataset_t:[8,10,12,13,17],becaus:[3,7,8],becom:[8,9,13],been:[7,9,13,15,17],beetween:8,befor:8,begin:[1,3,13],behalf:9,behaviour:[13,14],being:[8,10],believ:9,belong:[8,13],below:[1,16],benefici:9,best:8,better:[12,13],between:[1,8,12,13,17,18],binari:10,blk1:13,blk2:13,block:[8,10,13,15],blue:8,bnd2:17,board:10,bool:[13,15,16],both:[3,10,13,15,18],bound:8,boundari:[1,10,13,15],breath:7,bring:[9,13],brought:9,bucket:1,bug:12,build:[3,5,10],build_dir:7,built:16,bump_45:10,burden:3,busi:9,c:[1,3,4,6,8,9,13],cacheblock:15,cacheblocking2:15,call:[1,5,8,10,13,14,16,18],callback:12,can:[1,3,4,5,6,7,8,9,10,13,14,15,16],cartesian:[13,15],cassiope:[10,11,16],caus:[9,13],cconvert:10,ccx:13,ccy:13,ccz:13,cd:[5,7],cdot:[1,13],cell:[1,4,8,13,15],cell_cent:13,cell_fac:4,cell_renum_method:15,cell_vtx:4,cellcent:13,center:13,centers_to_nod:13,centertonod:13,certainli:3,cfd5:10,cfd:[8,10,11,18],cgn:[1,4,6,7,10,12,13,15,16,17],cgns_elmt_nam:15,cgns_io_tre:16,cgnsbase_t:[1,8,10,15],cgnscheck:10,cgnsconvert:10,cgnsname:15,cgnsnode:15,cgnstree:[13,15,16,17],cgnstree_t:1,chain:[10,18],chang:[13,14,18],charact:9,characterist:8,charg:9,check:[8,10],choic:[9,13],choos:[3,9,13],choosen:13,chosen:13,ci:7,circular:13,circumst:9,claim:9,clang:7,cleaner:7,clear:9,clone:7,close:8,closest:13,closestpoint:13,cloud:[12,13,15],cluster:[6,10],cmake:7,cmax:13,code:[3,4],coher:[4,7,8],collect:[1,8,13],color:[8,10],com:7,combin:9,come:[8,9,13],comfort:10,comm:[1,10,12,13,15,16,17],comm_world:[1,3,10,13,14,15,16,17],commerci:9,common:9,commonli:10,commun:[1,3,13,14,15,16,17],comparison:1,compat:[5,10,15],compil:[7,10,16],complet:[8,9],complianc:9,compliant:[8,9,10],compon:13,comput:[8,9,10,11,13,15,16,18],compute_balanced_weight:15,compute_cell_cent:13,compute_edge_cent:13,compute_face_cent:[12,13],compute_nosplit_weight:15,compute_regular_weight:15,compute_wall_dist:[10,13],computegrad:10,concaten:1,concatenate_jn:13,concept:10,concern:9,condit:[13,15],conf:[3,14],configur:[5,7,13],confirm:8,conflict:9,conform:13,conformize_jn_pair:13,connect:[1,4,8,10,12,15],connect_1to1_famili:13,connex:8,consecut:1,consequ:15,consequenti:9,consid:[8,17],consist:[1,10,11,18],consol:3,constant:[1,8],constitut:9,constraint:[8,13],constru:9,construct:6,consult:6,contain:[1,7,8,9,10,12,13,15,16,17],container_nam:13,containers_nam:[10,13],content:9,context:8,contigu:8,contract:9,contrari:8,contrast:8,contribut:[6,9,13],contributor:9,control:[9,13,15,16],convei:9,conveni:[3,15,17],convent:[1,2,3,10,13,16],convers:[12,18],convert:[10,16],convert_elements_to_mix:13,convert_elements_to_ngon:[1,12,13],convert_mixed_to_el:13,convert_s_to_ngon:[10,13],convert_s_to_u:13,coordin:[8,13,15],coordinatei:[8,16],coordinatex:[8,10],coordinatez:16,copi:[9,12,13,15],copyright:9,corner:12,correct:[3,5,9,12,13],correspond:[8,13,15,17,18],cost:[1,9],could:[1,8,18],count:[1,8],counter:9,court:9,cover:9,cpost:10,cpp:7,cpp_cgn:[5,7],cpp_cgns_unit_test:5,crazi:15,creat:[1,7,8,9,10,12,13,14,15,16,17],create_extractor_from_zsr:13,create_interpolator_from_part_tre:13,creation:9,cross:[9,13],cross_domain:13,ctest:5,ctransform:10,current:[8,15],curv:10,custom:15,cut:8,cuthil:15,cx:13,cy:13,cz:13,d:13,dai:9,daili:10,damag:9,data:[6,10,11,12,15,16,17,18],dataarrai:16,dataarray_t:[10,13,17],databas:10,datai:17,datax:17,dataz:17,dcmake_install_prefix:7,deadlock:14,deal:[8,9,16],death:9,debug:[15,16],decid:3,declar:[3,8],declaratori:9,decreas:13,dedic:10,def:3,defect:9,defend:9,defin:[6,8,9,10,13,15,17],definit:[8,17],delet:9,densiti:17,depend:[1,8,10,13],describ:[1,4,5,6,8,9,10,13,15,16,18],descript:[2,9,14,15,16],desir:9,destin:18,destructur:[6,13],detail:[0,6,7,9,14],detain:8,detect:[10,13,16],determin:1,dev:10,develop:[6,11],developp:[3,7],dic:13,dict:[13,15],dictionari:[15,17],dictionnari:[13,15],did:8,differ:[7,8,9,10,15,18],dimens:[8,13,15],dimension:15,dir:7,direct:[9,15],directli:[8,9,14,18],directori:9,dirichletdata:17,disabl:[13,14],disable_mpi_excepthook:14,discretedata:15,discretedata_t:[13,17],disk:18,dispatch:[15,16],displai:9,displaystyl:1,dispos:8,dist:[1,8,10,12,13,18],dist_to_full_tre:[12,15],dist_tre:[1,10,13,15,16,17],dist_tree_:13,dist_tree_bck:15,dist_tree_elt:1,dist_tree_gath:13,dist_tree_ini:13,dist_tree_src:13,dist_tree_tgt:13,dist_tree_to_fil:[10,16],dist_tree_to_part_tree_al:17,dist_tree_to_part_tree_only_label:17,dist_tree_u:13,dist_zon:[1,17],dist_zone_to_part_zones_al:17,dist_zone_to_part_zones_onli:17,distanc:[13,18],distinct:13,distinguis:1,distinguish:[8,9],distribut:[1,6,10,12,15,17,18],distribute_tre:12,distributor:9,disttre:[13,17],disttree_:13,dive:8,divid:[1,15,16],divis:8,doabl:3,doctest:7,doctrin:9,document:[7,9,13,15,16],doe:[3,7,8,9,13],domain:[8,13],domin:1,don:7,done:[1,7,13],download:[7,10],doxygen:7,dr:5,drafter:9,driver:16,dsi:10,dtype:13,due:8,dump:15,dump_pdm_output:15,duplic:[10,13],duplicate_from_periodic_jn:10,duplicate_from_rotation_jns_to_360:13,dure:[1,5,7],dynam:[7,11,14],e:[1,5,7,8,13,14,17,18],each:[1,3,4,8,9,10,12,13,15,16,17,18],earlier:9,eas:5,easiest:[14,16],easili:10,edg:[4,8,12,13,15],edge_cent:13,edge_length:15,edge_vtx:4,effect:[14,15],effici:10,either:[4,9,13,18],el8:10,el8_mpi:10,element:[1,4,8,10,12,13,15],element_t:[8,13],elementari:10,elementconnect:[1,8],elementrang:[8,13],elements_t:[8,13,15],elements_to_ngon:0,elementstartoffset:[1,10],elementtyp:13,elev:13,els:15,elsa:[10,11],elt:[4,12,13],elt_fac:4,elt_typ:13,elt_vtx:4,embed:16,empti:[8,10,15],enabl:12,enclos:13,encompass:3,encount:8,end:[1,8,9,17],enforc:9,enforce_ngon_pe_loc:13,englob:13,enough:3,enrich:18,ensur:13,entir:[8,9],entiti:[8,9,12,13,15],env:10,environ:[3,5,6,7,10,14],eq1:1,equal:[1,8,13,15,16],equat:[1,13],equilibr:15,equival:[3,8,9],error:[3,12,14],essenti:9,etc:[7,8,15],even:[9,16,18],event:9,everi:[8,9,15,17],everyth:17,exactli:[1,13],exampl:[4,8,10,13,15,16,17,18],except:[1,9,13,17],excepthook:14,exchang:[1,8,13,17],exchange_field:13,exclud:[7,9,13,17],exclude_dict:17,exclus:9,execut:[1,5,8,14],exercis:9,exist:[3,8,13,15,17,18],expect:[4,10,13,15,17],expected_:4,experiment:[1,13],explain:[8,14],explicitli:[8,9,10,13],exploit:9,expos:13,express:[9,15],extend:15,extens:[3,6,11],extent:9,exterior:1,extern:[1,5,7,13,15],extract:[6,10],extract_part_from_bc_nam:[10,13],extract_part_from_zsr:13,extract_tre:10,extract_tree_dist:10,extracted_bc:13,extracted_tre:13,extractor:13,f:[1,10,13],face:[4,6,8,13,15],face_cel:4,face_cent:13,face_renum_method:15,face_vtx:4,facecent:[1,13,15],facevtx:1,fact:8,factori:[10,12,13,16,17,18],factual:9,fail:[9,13],failur:9,fake:13,fals:[13,15,16],famili:[12,13],family_nam:13,family_t:13,familynam:[12,13],familyname_t:13,faster:1,featur:[10,13],fee:9,feel:3,feflo:[12,13],feflo_opt:13,few:[7,13,18],field:[8,10,13,15,18],fifti:9,figur:10,file:[5,7,8,9,10,12,14,18],file_print:[3,14],file_to_dist_tre:[1,10,12,13,15,16,17],file_to_part_tre:[12,16],filenam:[16,17],fill:[8,16],fill_size_tre:16,filter:[7,16,17],find:[1,13],find_closest_point:13,fine:8,finer:16,first:[1,6,8,9,12,13],firstsolut:17,fit:9,flat:[10,13],flowsolut:[13,15,17],flowsolution_t:[8,10,13,17],fluid:11,folder:[3,5],follow:[1,7,8,9,10,13,14,15,16,17],footnot:8,forc:14,form:8,formal:8,format:10,former:10,found:[7,10,13,16],foundat:9,fr:11,fraction:15,framework:11,free:9,friendli:10,from:[1,3,7,8,9,10,12,13,16,17,18],fsol:13,full:[8,13,14,16],full_onera_compat:13,full_to_dist_tre:[12,15],full_tre:15,fulli:[8,10],further:9,futur:13,g:[1,5,7,8,13,14,17,18],gather:[1,8,12,13,16],gc:[12,13],gc_t:[8,13],gcc:7,gener:[5,6,9,11,12,14,16,18],generate_dist_block:[10,12,13,15,16],generate_dist_point:15,generate_dist_spher:15,generate_jns_vertex_list:13,geodes:15,geometr:[8,10,15,17],get:[1,5,7,8,12,15],get_all_zone_t:[13,15,17],get_child_from_label:[10,15],get_child_from_nam:13,get_nam:13,get_node_from_label:[13,15],get_node_from_nam:[13,15],get_node_from_path:17,get_nodes_from_nam:13,get_rank:[3,13,15,16],get_siz:15,git:[5,7],git_config_submodul:[5,7],github:[7,11],gitlab:[6,7,10],give:[8,12],given:[8,9,10,13,15],global:[1,3,8,12,14,17],globalnumb:[8,15,16],gnu:9,gnum:[13,15],go:10,goe:8,good:8,goodwil:9,govern:9,gradient:10,grai:10,graph:15,graph_part_tool:[13,15],green:8,grid:[13,15],gridconnect:[12,13],gridconnectivity1to1_t:13,gridconnectivity_0_to_1:8,gridconnectivity_t:13,gridconnectivityproperti:13,gridcoordin:[13,15],gridcoordinate_t:13,gridcoordinates_t:8,gridloc:[1,13,15],gridlocation_t:[10,13],group:10,guarante:13,guid:16,h5py:16,ha:[7,8,9,12,13,16],hand:15,handl:3,happen:8,hard:4,have:[1,7,8,9,10,13,14,15,17],have_isolated_fac:13,hdf5:[7,8,16],hdf:[10,16],he:3,heavi:8,heavyweight:1,held:9,help:10,helper:7,helpful:11,here:[1,5,7,10],herebi:9,hereof:9,hesit:10,hessmach:13,hessmachxx:13,hessmachyi:13,hessmachyz:13,heterogen:[8,15],hex:1,hexa:[1,8,13],hexa_8:[4,15],high:[6,10,11,16,17],higher:[13,15],highest:16,hilbert:[13,15],hold:[4,8,15],homogen:15,host:6,how:[5,8,9,14,16],howev:[7,8,9,15,17],hpc:15,hpp:3,html:7,http:[7,9,11],hybrid:15,hyperplan:15,i:[1,8,9,13,15],i_rank:[13,15],icosahedr:15,id:[1,8,12,13,16],ident:1,idw:13,idw_pow:13,ie:[13,15],ii:9,illustr:[8,10,16],imagin:8,impi:10,implement:[8,13,15,18],impli:[1,3,9],implicit:[8,10],imposs:9,inaccuraci:9,incident:9,includ:[1,3,8,9,10,13,15,17],include_dict:17,increas:13,incur:9,indemn:9,indemnifi:9,independ:[8,13,16],index:[4,8],indic:[8,13,16],indirect:9,indirectli:9,indistinctli:13,individu:9,induc:8,info:[3,7,14],inform:[1,8,9,12,14,15,16],informat:7,infra:7,infring:9,init:[5,7,13],initi:[9,13,15],injuri:9,inplac:[13,17,18],input:[1,10,13,15,18],inria:13,insert:[1,16],insid:8,instal:[10,13,15,16],install_dir:7,instanc:[3,7,8],instanci:[3,13],instead:7,instruct:14,insur:10,int32:13,integ:[8,10,15],intel2120:10,intel:[7,10],intellectu:9,intend:9,interfac:15,interlac:13,intern:[1,8,13],interpolate_from_part_tre:13,interpret:3,interv:[4,8],introduc:8,introduct:[6,10],intuit:8,invers:[13,16],invert:1,investig:1,involv:[1,10,13],io:[1,6,10,11,12,13,15,17],irrelev:15,irrespect:13,is_same_tre:15,iso_field:13,iso_part_tre:13,iso_surfac:13,iso_v:13,isobarycent:13,isol:13,isosurf:13,isosurf_tre:13,isosurfac:[12,13],isotrop:13,issu:[6,10,13],iter_all_zone_t:13,ith:8,its:[1,3,7,8,9,10,13,15],itself:[3,8,16],j:[7,8],jn:13,jn_path:13,jn_paths_for_dupl:13,job:10,join:[13,17],joint:[1,5],jth:8,judgment:9,judici:9,jurisdict:9,just:[1,7,8],k:1,keep:[1,8,13,15,16],kei:[8,13,17],kept:1,keyword:15,kind:[1,6,7,8,9,13,15,17,18],know:8,knowledg:8,known:[9,15,17],kwarg:[13,15],label:[1,13,17],languag:9,larg:[10,13,15],last:7,lastli:17,later:9,latest:10,latter:3,law:9,ld_library_path:[5,7],lead:[13,15],least:15,left:[1,15],legaci:[8,10,13,16],legal:9,len:[13,15],length:[8,15],less:[8,15],lesser:9,let:[1,8,16],level:[5,6,8,10,11,13,15,16],lexicograph:[1,15],liabl:9,librari:[3,4,6,7,8,10,11,15,16],light:14,lightweight:8,like:[3,7,8,9,14],limit:[8,13],line:[7,10],link:[8,12,16],list:[13,15,16,17],lista:13,listb:13,listen:3,ln:8,ln_to_gn:8,load:[8,10,15,16,18],load_collective_size_tre:16,loc:13,loc_contain:13,loc_toler:13,local:[1,3,4,8,13],localize_point:13,locat:[7,8,9,10,12,13,15],locationandclosest:13,log:[1,2],logger:14,logging_conf_fil:[3,14],logo:9,look:[1,3,8,9,10,14],loss:9,lost:9,low:[8,11,16],lower:[1,13],lowest:13,m:[15,17],machin:7,made:[6,9,13],magic:17,mai:[7,8,9,13,16,17],maia:[1,5,7,10,12,13,14,15,16,17,18],maia_build_fold:7,maia_doctest_unit_test:5,maia_fold:[5,7],maia_poly_new_to_old:10,maia_poly_old_to_new:10,main:[6,16,18],maintain:[9,10],make:[7,8,9,13],malfunct:9,manag:[2,6,7,8,9,12,14,17],mandatori:13,mani:15,manner:[9,16],manual:[6,10],manuel:18,map:[8,10,13,17],mark:9,match:13,matcha:[10,13],matchb:[10,13],materi:[9,10],mathcal:1,mathtt:8,matrix:13,matter:9,max_coord:15,maximum:9,mean:[1,4,7,8,9,10,13,15,17],memori:[1,8,12,13,14],merchant:9,merg:[10,12,13],merge_all_zones_from_famili:13,merge_connected_zon:[10,13],merge_strategi:12,merge_zon:[12,13],merge_zones_from_famili:13,merged_zon:13,mergedzon:13,mesh:[1,4,11,12,15,17],mesh_dir:[1,13,15],mesh_fil:10,messag:3,metadata:13,method:[3,8,9,12,13,15,17,18],metric:13,mid:11,mind:13,minim:[10,13,15],miss:[13,15,17],mix:[1,8,10,13],mlog:3,modif:9,modifi:[1,13,18],modul:[7,10,11,12,16,18],momentum:17,momentumi:17,momentumx:17,momentumz:17,more:[3,6,8,9,13,14,15,17],moreov:9,most:[3,6,8,10],move:13,move_field:13,mpart:15,mpi4pi:[1,7,10,13,15,16,17],mpi:[1,3,5,7,10,13,14,15,16,17],mpi_abort:14,mpi_comm:1,mpi_file_print:3,mpi_rank_0_stderr_print:[3,14],mpi_rank_0_stdout_print:[3,14],mpi_stderr_print:3,mpi_stdout_print:[3,14],mpicomm:[13,15,16,17],mpirun:5,mpl:9,msg:3,much:[1,8,10],multipl:[10,13],must:[7,8,9,10,13,15,16,17],my:3,my_app:3,my_fil:3,my_logg:3,my_print:3,my_them:3,mybas:8,mynod:8,myzon:8,n0:8,n1:10,n1xn2:10,n2:10,n:[1,8,13],n_:1,n_cell:13,n_cell_in_sect:1,n_cell_per_cach:15,n_face_of_cell_typ:1,n_face_per_pack:15,n_i:1,n_part:15,n_part_tot:15,n_proc:4,n_rank:[8,15],n_thing:4,n_vtx:[4,15],naca0012:13,name:[3,7,9,13,15,16],nan:13,natur:18,ncell:13,nearest:13,nearli:15,necessari:9,necessarili:8,need:[1,3,7,8,13,15,17],neglig:9,neighbour:13,net:7,never:8,new_child:13,new_dataarrai:13,new_flowsolut:13,new_nod:13,new_zonesubregion:13,next:[8,10,17],nface:[8,12,13,15],nface_n:[4,10,13,15],nface_to_p:13,nfaceel:13,nfacenod:13,ngon:[8,10,12,13,15],ngon_n:[10,13,15],ngonel:15,ngons_to_el:13,nodal:13,node:[1,8,10,12,13,15,16,17],non:[8,9,10,15],none:[3,12,13,15,17],norm:8,normal:1,notabl:[5,8],notat:11,note:[1,7,8,9,10,13,14,16],noth:9,notic:8,notifi:9,notwithstand:9,now:[1,3,8,10,13,16],np:[5,13],number:[1,4,5,9,12,13,15],numpi:13,ny:8,o:1,object:[3,8,12,13],oblig:9,observ:10,obtain:[9,15],occur:[1,12,13,14],odd:13,off:1,offer:9,often:[5,7,8,10,18],old:[10,13],onc:[1,7,10,13],one:[1,7,8,9,10,13,16,17],onera:[6,7,11,13],onera_spack_repo:7,ones:[1,5,7,8,13,15],ongo:9,onli:[1,4,8,9,13,15,16,17,18],only_uniform:15,open:[4,6,8],oper:[1,10,13,15,16,17,18],opposit:[10,13],opposite_jn:10,option:[8,9,12,13,17],order:[1,5,6,8,9,10,13,15],ordinari:9,org:9,organ:5,orient:[3,15],origin:[1,8,9,13,15],os:17,other:[5,8,9,10,13,15],otherwis:[9,13,15,16],our:8,out:[7,10],out_fs_nam:13,outlin:10,output:[3,13,14,15,16],output_path:13,outstand:9,over:[1,6,8,13,15,16],overrid:[7,14],overview:6,own:[7,8,9,10,14,18],owner:15,ownership:9,p0:8,p1:8,p2:8,p:13,packag:[7,10],page:[6,12,14],pair:13,paradigm:[7,8,15],paradigma:13,parallel:[6,10,12,13,15,16,18],parallel_sort:1,paramet:[12,13,15,16,17],parameter:5,parametr:13,parent:[1,4,10,13,15],parentel:[1,4,13],parentelementsposit:1,parentposit:1,parmeti:[13,15],part:[6,8,9,10,12,13,15,18],part_interface_loc:15,part_tre:[10,13,15,16,17],part_tree_iso:13,part_tree_src:13,part_tree_tgt:13,part_tree_to_dist_tree_al:[10,17],part_tree_to_dist_tree_only_label:17,part_tree_to_fil:16,part_zon:[13,17],part_zones_to_dist_zone_al:17,part_zones_to_dist_zone_onli:17,parti:[7,9],partial:[1,8],particular:[1,3,7,9],partion:13,partit:[1,6,10,12,17,18],partition_dist_tre:[10,13,15,16,17],pass:13,patch:17,patent:9,path:[7,10,13,15,16,17],pattern:1,pe:15,pe_to_nfac:[10,13],peak:1,penalti:8,penta_6:15,per:[8,15],percent:9,perfect:[1,6],perform:[9,10,12,13,15,16],perio:13,period:[12,13],permit:9,person:9,physic:15,pi:13,pick:17,pictur:10,piec:8,pip3:7,place:[1,9,13],plane:[10,13],plane_eq:13,plane_slic:[10,13],planeslic:10,point:[5,8,12,13,15],point_cloud:[10,13],point_list:13,pointlist:[1,8,13],pointlistdonor:13,pointrang:8,pointrange_t:10,poli:[13,15,16],polici:[12,13],poly_new_to_old:[12,13],poly_old_to_new:[12,13],polyedr:13,polygon:13,polyhedr:[10,13],polyhedra:15,poor:[12,15],popul:7,portabl:11,portion:9,posit:[1,8,15],possibl:[8,9,10,12,15,17],post:[10,11],power:[9,13],practic:[1,7,10],pre:[1,10,11,15],prefer:[9,10],prefix:3,present:[7,8,13],preserv:[8,10,13],preserve_orient:[13,15],pressur:[13,17],prevent:[9,12],previou:[1,8],previous:1,princip:9,print:3,printer:14,prior:9,prism:[1,13],privileg:16,proc:[4,8,13,15],process:[1,4,5,7,8,9,10,11,13,14,15,16],produc:[8,13,15,16,18],product:10,profit:9,program:3,progress:11,prohibit:9,project:13,project_build_dir:5,project_src_dir:5,project_util:7,propag:13,properli:8,properti:[1,8,9],prove:9,provid:[0,3,6,7,8,9,10,11,13,14,15,16,17,18],provis:9,provision:9,pt:[10,13,15,17],ptscotch:[13,15],publish:9,pure:3,purpl:8,purpos:[9,14,16],put:[3,8,9,10],px:8,pybind11:7,pycgn:10,pyra:[1,13],pyra_5:[13,15],pytest:[5,7],pytest_check:7,pytest_parallel:[7,12],python3:7,python:[3,4,6,7,8,10,11,14,16],pythonpath:[5,7],pytre:[10,13,15,16,17],quad:[1,8,13],quad_4:[8,13,15],quad_4_interior:1,qualiti:9,quantiti:8,quarter_crown_square_8:17,quick:6,quickselect:1,quit:7,r8:13,r:13,radiu:15,rais:14,rand:13,random:[13,15],rang:[7,13,15],rank:[1,8,14,15,16,17],rank_id:15,rather:3,raw:15,read:[3,10,12,13,16],read_link:[12,16],read_tre:[15,16],readi:[7,10],rearang:13,rearrange_element_sect:13,reason:[8,9],receipt:9,receiv:[1,3,9,10,13,15],recip:7,recipi:9,recommend:10,reconstitut:13,reconstruct:[8,12],record:13,recov:12,recover_dist_tre:[10,15],red:8,redispatch:16,redistribut:13,redistribute_tre:[12,13],reduc:[12,13],reduct:13,redund:8,refer:[3,8,9,13,15],referencest:12,referencestate_t:13,reform:9,regener:15,regular:[1,8,13],reinstat:9,rel:13,relat:[8,9,12,13,16,17],relax:8,releas:10,relev:[9,13,15,17],reli:[10,11],remain:13,remedi:9,remot:1,remov:[7,9,12,13,16],remove_p:13,removenfac:13,removep:13,renam:[9,12,13,16],renumb:[8,15],repair:9,repart:13,repartit:8,replac:[1,13],replic:8,repo:7,report:[10,13,15],repositori:[5,6,7],repres:[8,9,10,13],reproduc:9,request:[13,15,16],requir:[1,7,8,9,13,17],resel:9,reshap:13,resp:[13,17],respect:[3,8,9,10],restrict:[9,13],result:[1,8,9,13,18],retriev:[6,11,13],revers:15,right:[1,9,15],risk:9,rk:3,rm_nodes_from_nam:[13,16],robust:12,room:10,root:13,rotat:13,rotation_angl:13,rotation_cent:13,round:8,royalti:9,ruamel:7,rule:17,run:[5,10,13],s:[6,7,8,9,10,12,13,15,16,17],s_twoblock:[10,13,15],said:8,sale:9,same:[1,7,8,10,13,15],sampl:[10,16],sample_mesh_dir:17,sator:10,save:[10,18],scalar:[13,15],scale:1,scatter:1,scenario:8,scientif:11,scratchm:10,script:[3,10],sdtout:14,search:13,second:[8,13],secondsolut:17,section:[0,8,9,10,13,17],see:[1,7,8,9,10,13,14,15,16],seen:8,select:[3,12,13],self:[3,10],sell:9,semi:[4,8],send:[1,10],sens:8,separ:[9,16],seq:13,sequenc:13,sequenti:[1,10,15,16],servic:[9,11],set:[3,10,11,13,14,15],set_nam:10,setup:10,sever:[3,7,8,10],sh:[5,7,10],shall:9,shape:[8,10,13,16],share:[8,9,12,13],shift:[1,13],shortcut:13,should:[1,3,7,9,10,13,15,16],show:16,sid:[10,13,16],side1:13,side2:13,side:13,similar:[1,13,17],similarli:[10,13],simpl:10,simpli:[8,15],sin:10,sinc:[1,8,10],singl:[8,10,13,14,15,16],single_fil:16,singular:4,size:[1,4,8,10,12,13,15,16],size_tre:16,skeleton:8,skill:9,slice:[10,13],slice_tre:[10,13],small:[15,18],smaller:12,snake_cas:4,so:[3,4,7,8,9],socl:10,softwar:[6,9,11,13],solut:13,solver:[8,18],some:[0,6,7,8,9,10,12,13,14,15,16,18],someth:[1,8],sometim:8,sonic:10,sort:[1,8],sort_by_rank:1,sort_int_ext:15,sourc:[5,6,10,13,17,18],spack_root:7,spacki:7,span:8,spane:8,special:[9,13],specialsolut:17,specif:[9,15],specifi:[13,17],speedup:1,sphere:[13,15],sphere_eq:13,spheric:[12,13,15],spherical_slic:13,sphinx:7,spiro:10,split:[1,8,10,12,15],splitabl:8,spot:1,squar:8,src_dir:7,src_sol:13,src_tree:13,stable_sort:[1,13],stage:1,standalon:10,standard:[1,6,8,10,11,12,13,15],start:[3,6,8,13,17],start_rang:13,stat:[3,14],statutori:9,std:7,std_e:[1,3,5,7],std_e_unit_test:5,stderr:[3,14],stderr_print:3,stdout:3,stdout_print:3,step:[1,7,13,16],steward:9,still:[3,13],stoppag:9,storag:11,store:[1,8,13,16],str:[13,15,16,17],strategi:13,strict:15,stride:8,string:[3,13],structur:[4,8,10,12,13,15,16,17,18],sub:[8,14],subfil:16,subject:9,sublicens:9,submesh:[8,13],submodul:5,subregion:[8,15],subset:[8,13],subset_loc:13,subset_merg:13,substanc:9,successfulli:13,suffici:9,suffix:[4,13],suit:8,sum:[1,8,15],sum_:1,summar:14,support:[9,12,13,15,16,17],suppos:[13,17],sure:7,surfac:[10,12,13,15],surviv:9,sy:14,system:[7,11],sz:4,t:[7,13],t_:1,t_p:1,tabl:14,take:[1,8,12,13,17],target:[13,15,17],tata:17,tell:7,tensor:13,tensori:13,term:8,termin:14,test:[3,4,10],test_util:[1,13,15,17],tet:[1,13],tetra:1,tetra_4:[10,13,15],text:9,textrm:1,tgt_sol:13,tgt_tree:13,tgt_zone:13,than:[1,3,8,9,12,13],thank:[1,8,10,13],thei:[1,3,7,8,9,10,13,15,16,17,18],them:[1,3,7,8,13,16],theme:3,theoret:1,theori:9,therefor:8,thereof:9,theses:10,thi:[0,1,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18],thing:4,third:[7,9],those:[1,8,9,13,15,17],thought:13,three:[1,15,18],through:[8,13,14,15,18],thu:[8,13,16,18],tild:13,time:[1,3,8,9,14],tl:5,tmp:7,tmp_user:10,tol:13,toler:[10,13],too:[8,15],tool:[10,15,18],topolog:[10,15],tort:9,total:[8,15],total_s:8,track:[6,8],trade:1,trademark:9,transfer:[8,9,10,12,13,15,18],transfer_dataset:13,transform:[6,10],transform_affin:13,translat:13,transmit:15,transpar:7,treat:8,treatement:13,tree:[1,6,10,12,13,16,18],tree_:10,tree_u:10,tri:[1,13],tri_3:[13,15],tri_3_interior:1,tune:13,tupl:13,turbulentdist:13,tutuz:17,twice:[1,3],two:[1,4,8,13,16,17,18],type:[1,3,8,13,15],typic:[3,7,8,18],u:[13,15],u_atb_45:[10,13],u_naca0012_multizon:13,u_twoblock:10,uelt_m6w:[1,13],unabl:10,unbalanc:13,uncatch:14,unchang:13,under:[1,6,9,10,12,13],underli:[10,13],understand:9,undesir:14,unenforc:9,unfil:8,unfortun:4,uniform:[13,15],union:8,uniqu:[1,13,16],unit:4,unless:9,unloc:13,unmatch:13,unmodifi:9,unpartit:8,unsign:[12,13],unstructur:[1,8,10,13,15,18],unstuctur:15,until:[3,8,9,13],unwant:[12,16],up:[1,3,10,13],updat:[5,7,13,18],us:[1,3,4,5,6,7,8,10,12,13,14,15,16,17,18],usag:[10,14],user:[3,6,7,9,10,13,14,15],usr:10,usual:4,util:[1,3,7,10,13,15,17],v3:7,v5:10,v:[9,13],valid:13,validli:9,valu:[8,12,13,15],vari:1,variabl:[3,4,8,14],variant:1,variou:[6,13],vector:[13,15],vectori:13,verbos:5,veri:1,versa:3,version:[7,8,10,12],vertex:[4,8,10,12,13,15],vertic:[1,4,8,13,15],vice:3,view:[1,7,8],virtual:8,vol_rank:13,volum:[10,12,13],vtx:[4,13],vtx_renum_method:15,vtx_sol:13,wa:[1,8,9,15],wai:[7,8,14,16],wall:[10,13,18],wall_dist:12,walldist:[10,13],want:[3,5,6,7,8,9,10,16],warn:[3,13,14],we:[1,3,4,5,7,8,10,13,16,17],weight:[13,15],well:[8,15,16],were:[1,8,13],what:[3,8,12,16],when:[3,4,8,12,13,14,15,17],where:[1,3,7,8,9,13,15],whether:9,which:[1,5,8,9,10,11,13,14,15,16,17,18],white:10,who:[3,9,13,15],whose:[8,13,18],why:10,wide:9,wildcard:[12,13,17],within:[8,9,10,13,15,16],without:[7,9,15],work:[7,10],workflow:[2,10,16,18],world:[9,10],wors:1,worst:8,worth:3,would:[1,3,8,9],wrap:12,writ:8,write:[11,12,16],write_tre:16,written:[4,8,16],x:[8,10,13],x_0:13,xx:13,xy:13,xz:13,y:[8,13],y_0:13,yaml:[1,7,13,15,17],yet:[8,13,15],ym:16,you:[6,7,8,9,10,13,18],your:[7,9,10,13,14,16,18],yy:13,yz:13,z:13,z_0:13,zero:[13,15],zgc:12,zm:16,zone0:8,zone1:8,zone:[1,8,13,15,16],zone_path:[12,13],zone_t:[8,13,15,17],zone_to_part:15,zone_typ:15,zonebc:[15,17],zonegridconnect:[10,15],zonenam:13,zonesubregion:[13,15,17],zonesubregion_t:[8,13,17],zsr_name:13,zz:13},titles:["Algorithms description","elements_to_ngons","Developer Manual","Log management","Conventions","Development workflow","Welcome to Maia!","Installation","Introduction","License","Quick start","Related projects","Release notes","Algo module","Configuration","Factory module","File management","Transfer module","User Manual"],titleterms:{"0":[9,12],"1":[9,12],"10":9,"2":[9,12],"2023":12,"3":9,"4":9,"5":9,"6":9,"7":9,"8":9,"9":9,"cassiop\u00e9":11,"new":[9,12],"public":9,A:9,With:9,addit:9,advanc:12,algo:13,algorithm:[0,1,13],all:1,altern:1,api:12,applic:[8,9],argument:1,avail:3,b:9,build:7,calcul:13,cellfac:1,cgn:[8,11],chang:12,code:9,complex:1,compli:9,comput:1,concept:8,condit:9,configur:[3,14],connect:13,convent:[4,8],convers:13,core:8,creat:3,data:[8,13],date:9,definit:9,depend:7,descript:[0,1],design:1,dev:12,develop:[2,5,7],disclaim:9,distribut:[8,9,13,16],divid:8,document:6,due:9,effect:9,elements_to_ngon:1,environn:10,exampl:1,except:14,execut:9,exhibit:9,explan:1,extract:13,face:1,factori:15,fair:9,featur:12,field:17,file:[3,16],fix:12,folder:7,form:9,from:15,full:15,gener:[1,13,15],geometr:13,geometri:13,grant:9,handl:14,highlight:10,improv:12,inabl:9,incompat:9,instal:7,interfac:13,interior:1,interpol:13,introduct:8,io:16,juli:12,larger:9,launch:5,level:17,liabil:9,licens:9,limit:9,litig:9,log:[3,14],logger:3,mai:12,maia:[3,6,8],manag:[3,13,15,16],manual:[2,7,18],march:12,mesh:[8,10,13],miscellan:9,modifi:9,modul:[5,13,15,17],mozilla:9,mpi:8,name:[4,8],nface:1,ngon:1,note:12,notic:9,number:8,option:[7,15],other:[4,7],output:1,overview:8,own:3,paradigm:11,parallel:[1,8],partit:[8,13,15,16],prefer:7,printer:3,procedur:7,project:11,quick:10,raw:16,recov:15,regul:9,relat:11,releas:12,reorder:15,repartit:15,represent:9,resourc:10,respons:9,scope:9,secondari:9,section:1,sequenti:13,simplifi:1,sourc:[7,9],spack:7,specif:[3,8],start:10,statut:9,sub:5,submodul:7,subsequ:9,summari:6,support:10,term:9,termin:9,test:5,through:7,tool:13,transfer:17,transform:[1,13],tree:[8,15,17],troubleshout:10,us:9,user:[12,18],v1:12,version:9,warranti:9,welcom:6,work:9,workflow:[5,7],your:3,zone:17}}) \ No newline at end of file diff --git a/docs/1.2/user_manual/algo.html b/docs/1.2/user_manual/algo.html new file mode 100644 index 00000000..fa2c635b --- /dev/null +++ b/docs/1.2/user_manual/algo.html @@ -0,0 +1,1613 @@ + + + + + + + + + + Algo module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Algo module

+

The maia.algo module provides various algorithms to be applied to one of the +two kind of trees defined by Maia:

+
    +
  • maia.algo.dist module contains some operations applying on distributed trees

  • +
  • maia.algo.part module contains some operations applying on partitioned trees

  • +
+

In addition, some algorithms can be applied indistinctly to distributed or partitioned trees. +These algorithms are accessible through the maia.algo module.

+

The maia.algo.seq module contains a few sequential utility algorithms.

+
+

Distributed algorithms

+

The following algorithms applies on maia distributed trees.

+
+

Connectivities conversions

+
+
+convert_s_to_u(disttree_s, connectivity, comm, subset_loc={})
+

Performs the destructuration of the input dist_tree.

+

This function copies the GridCoordinate_t and (full) FlowSolution_t nodes, +generate a NGon based connectivity and create a PointList for the following +subset nodes: +BC_t, BCDataSet_t and GridConnectivity1to1_t. +In addition, a PointListDonor node is generated for GridConnectivity_t nodes.

+

Metadata nodes (“FamilyName_t”, “ReferenceState_t”, …) at zone and base level +are also reported on the unstructured tree.

+
+

Note

+

Exists also as convert_s_to_ngon() with connectivity set to +NGON_n and subset_loc set to FaceCenter.

+
+
+
Parameters
+
    +
  • disttree_s (CGNSTree) – Structured tree

  • +
  • connectivity (str) – Type of elements used to describe the connectivity. +Admissible values are "NGON_n" and "HEXA" (not yet implemented).

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • subset_loc (dict, optional) – Expected output GridLocation for the following subset nodes: BC_t, GC_t. +For each label, output location can be a single location value, a list +of locations or None to preserve the input value. Defaults to None.

  • +
+
+
Returns
+

CGNSTree – Unstructured disttree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree_s = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+dist_tree_u = maia.algo.dist.convert_s_to_u(dist_tree_s, 'NGON_n', MPI.COMM_WORLD)
+for zone in maia.pytree.get_all_Zone_t(dist_tree_u):
+  assert maia.pytree.Zone.Type(zone) == "Unstructured"
+
+
+
+ +
+
+convert_elements_to_ngon(dist_tree, comm, stable_sort=False)
+

Transform an element based connectivity into a polyedric (NGon based) +connectivity.

+

Tree is modified in place : standard element are removed from the zones +and the PointList are updated. If stable_sort is True, face based PointList +keep their original values.

+

Requirement : the Element_t nodes appearing in the distributed zones +must be ordered according to their dimension (either increasing or +decreasing).

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by standard elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • stable_sort (bool, optional) – If True, 2D elements described in the +elements section keep their original id. Defaults to False.

  • +
+
+
+

Note that stable_sort is an experimental feature that brings the additional +constraints:

+
+
    +
  • 2D meshes are not supported;

  • +
  • 2D sections must have lower ElementRange than 3D sections.

  • +
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+ngons_to_elements(t, comm)
+

Transform a polyedric (NGon) based connectivity into a standard nodal +connectivity.

+

Tree is modified in place : Polyedric element are removed from the zones +and Pointlist (under the BC_t nodes) are updated.

+

Requirement : polygonal elements are supposed to describe only standard +elements (ie tris, quads, tets, pyras, prisms and hexas)

+

WARNING: this function has not been parallelized yet

+
+
Parameters
+
    +
  • disttree (CGNSTree) – Tree with connectivity described by NGons

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +
+
+convert_elements_to_mixed(dist_tree, comm)
+

Transform an element based connectivity into a mixed connectivity.

+

Tree is modified in place : standard elements are removed from the zones. +Note that the original ordering of elements is preserved.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by standard elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+convert_mixed_to_elements(dist_tree, comm)
+

Transform a mixed connectivity into an element based connectivity.

+

Tree is modified in place : mixed elements are removed from the zones +and the PointList are updated.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by mixed elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD)
+maia.algo.dist.convert_mixed_to_elements(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+rearrange_element_sections(dist_tree, comm)
+

Rearanges Elements_t sections such that for each zone, +sections are ordered in ascending dimensions order +and there is only one section by ElementType. +Sections are renamed based on their ElementType.

+

The tree is modified in place. +The Elements_t nodes are guaranteed to be ordered by ascending ElementRange.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with an element-based connectivity

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_block(11, 'PYRA_5', MPI.COMM_WORLD)
+pyras = PT.get_node_from_name(dist_tree, 'PYRA_5.0')
+assert PT.Element.Range(pyras)[0] == 1 #Until now 3D elements are first
+
+maia.algo.dist.rearrange_element_sections(dist_tree, MPI.COMM_WORLD)
+tris = PT.get_node_from_name(dist_tree, 'TRI_3') #Now 2D elements are first
+assert PT.Element.Range(tris)[0] == 1
+
+
+
+ +
+
+generate_jns_vertex_list(dist_tree, comm, have_isolated_faces=False)
+

For each 1to1 FaceCenter matching join found in the distributed tree, +create a corresponding 1to1 Vertex matching join.

+

Input tree is modified inplace: Vertex GridConnectivity_t nodes +are stored under distinct containers named from the original ones, suffixed +with #Vtx. Similarly, vertex GC nodes uses the original name suffixed +with #Vtx.

+

Only unstructured-NGon based meshes are supported.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • have_isolated_faces (bool, optional) – Indicate if original joins includes +faces who does not share any edge with other external (join) faces. +If False, disable the special treatement needed by such faces (better performances, +but will fail if isolated faces were actually present). +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree_s = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+dist_tree = maia.algo.dist.convert_s_to_ngon(dist_tree_s, MPI.COMM_WORLD)
+
+maia.algo.dist.generate_jns_vertex_list(dist_tree, MPI.COMM_WORLD)
+assert len(maia.pytree.get_nodes_from_name(dist_tree, 'match*#Vtx')) == 2
+
+
+
+ +
+
+

Geometry transformations

+
+
+duplicate_from_rotation_jns_to_360(dist_tree, zone_paths, jn_paths_for_dupl, comm, conformize=False, apply_to_fields=False)
+

Reconstitute a circular mesh from an angular section of the geometry.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input distributed tree

  • +
  • zone_paths (list of str) – List of pathes (BaseName/ZoneName) of the connected zones to duplicate

  • +
  • jn_paths_for_dupl (pair of list of str) – (listA, listB) where listA (resp. list B) stores all the +pathes of the GridConnectivity nodes defining the first (resp. second) side of a periodic match.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • conformize (bool, optional) – If true, ensure that the generated interface vertices have exactly same +coordinates (see conformize_jn_pair()). Defaults to False.

  • +
  • apply_to_fields (bool, optional) – See maia.algo.transform_affine(). Defaults to False.

  • +
+
+
+
+ +
+
+merge_zones(tree, zone_paths, comm, output_path=None, subset_merge='name', concatenate_jns=True)
+

Merge the given zones into a single one.

+

Input tree is modified inplace : original zones will be removed from the tree and replaced +by the merged zone. Merged zone is added with name MergedZone under the first involved Base +except if output_path is not None : in this case, the provided path defines the base and zone name +of the merged block.

+

Subsets of the merged block can be reduced thanks to subset_merge parameter:

+
    +
  • None : no reduction occurs : all subset of all original zones remains on merged zone, with a +numbering suffix.

  • +
  • 'name' : Subset having the same name on the original zones (within a same label) produces +and unique subset on the output merged zone.

  • +
+

Only unstructured-NGon trees are supported, and interfaces between the zones +to merge must have a FaceCenter location.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • zone_paths (list of str) – List of path (BaseName/ZoneName) of the zones to merge. +Wildcard * are allowed in BaseName and/or ZoneName.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • output_path (str, optional) – Path of the output merged block. Defaults to None.

  • +
  • subset_merge (str, optional) – Merging strategy for the subsets. Defaults to ‘name’.

  • +
  • concatenate_jns (bool, optional) – if True, reduce the multiple 1to1 matching joins related +to the merged_zone to a single one. Defaults to True.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 3
+
+maia.algo.dist.merge_zones(dist_tree, ["BaseA/blk1", "BaseB/blk2"], MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 2
+
+
+
+ +
+
+merge_zones_from_family(tree, family_name, comm, **kwargs)
+

Merge the zones belonging to the given family into a single one.

+

See merge_zones() for full documentation.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • family_name (str) – Name of the family (read from FamilyName_t node) +used to select the zones.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • kwargs – any argument of merge_zones(), excepted output_path

  • +
+
+
+
+

See also

+

Function merge_all_zones_from_families(tree, comm, **kwargs) does +this operation for all the Family_t nodes of the input tree.

+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+
+# FamilyName are not included in the mesh
+for zone in PT.get_all_Zone_t(dist_tree):
+  PT.new_child(zone, 'FamilyName', 'FamilyName_t', 'Naca0012')  
+
+maia.algo.dist.merge_zones_from_family(dist_tree, 'Naca0012', MPI.COMM_WORLD)
+
+zones = PT.get_all_Zone_t(dist_tree)
+assert len(zones) == 1 and PT.get_name(zones[0]) == 'naca0012'
+
+
+
+ +
+
+merge_connected_zones(tree, comm, **kwargs)
+

Detect all the zones connected through 1to1 matching jns and merge them.

+

See merge_zones() for full documentation.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • kwargs – any argument of merge_zones(), excepted output_path

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+
+maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 1
+
+
+
+ +
+
+conformize_jn_pair(dist_tree, jn_paths, comm)
+

Ensure that the vertices belonging to the two sides of a 1to1 GridConnectivity +have the same coordinates.

+

Matching join with Vertex or FaceCenter location are admitted. Coordinates +of vertices are made equal by computing the arithmetic mean of the two +values.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input tree

  • +
  • jn_pathes (list of str) – Pathes of the two matching GridConnectivity_t +nodes. Pathes must start from the root of the tree.

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +
+
+adapt_mesh_with_feflo(dist_tree, metric, comm, container_names=[], feflo_opts='')
+

Run a mesh adaptation step using Feflo.a software.

+
+

Important

+
    +
  • Feflo.a is an Inria software which must be installed by you and exposed in your $PATH.

  • +
  • This API is experimental. It may change in the future.

  • +
+
+

Input tree must be unstructured and have an element connectivity. +Boundary conditions other than Vertex located are managed.

+

Adapted mesh is returned as an independant distributed tree.

+

Setting the metric

+

Metric choice is available through the metric argument, which can take the following values:

+
    +
  • None : isotropic adaptation is performed

  • +
  • str : path (starting a Zone_t level) to a scalar or tensorial vertex located field:

    +
      +
    • If the path leads to a scalar field (e.g. FlowSolution/Pressure), a metric is computed by +Feflo from this field.

    • +
    • If the path leads to a tensorial field (e.g. FlowSolution/HessMach), we collect its 6 component (named +after CGNS tensor conventions) and pass it to +Feflo as a user-defined metric.

    • +
    +
    FlowSolution FlowSolution_t
    +├───GridLocation GridLocation_t "Vertex"
    +├───Pressure DataArray_t R8 (100,)
    +├───HessMachXX DataArray_t R8 (100,)
    +├───HessMachYY DataArray_t R8 (100,)
    +├───...
    +└───HessMachYZ DataArray_t R8 (100,)
    +
    +
    +
  • +
  • list of 6 str : each string must be a path to a vertex located field representing one component +of the user-defined metric tensor (expected order is XX, XY, XZ, YY, YZ, ZZ)

  • +
+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree to be adapted. Only U-Elements +single zone trees are managed.

  • +
  • metric (str or list) – Path(s) to metric fields (see above)

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • container_names (list of str) – Name of some Vertex located FlowSolution to project on the adapted mesh

  • +
  • feflo_opts (str) – Additional arguments passed to Feflo

  • +
+
+
Returns
+

CGNSTree – Adapted mesh (distributed)

+
+
+
+

Warning

+

Although this function interface is parallel, keep in mind that Feflo.a is a sequential tool. +Input tree is thus internally gathered to a single process, which can cause memory issues on large cases.

+
+

Example

+
import mpi4py.MPI as MPI
+import maia
+import maia.pytree as PT
+
+from maia.algo.dist import adapt_mesh_with_feflo
+
+dist_tree = maia.factory.generate_dist_block(5, 'TETRA_4', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+
+# > Create a metric field
+cx, cy, cz = PT.Zone.coordinates(zone)
+fields= {'metric' : (cx-0.5)**5+(cy-0.5)**5 - 1}
+PT.new_FlowSolution("FlowSolution", loc="Vertex", fields=fields, parent=zone)
+
+# > Adapt mesh according to scalar metric
+adpt_dist_tree = adapt_mesh_with_feflo(dist_tree,
+                                       "FlowSolution/metric",
+                                       MPI.COMM_WORLD,
+                                       container_names=["FlowSolution"],
+                                       feflo_opts="-c 100 -cmax 100 -p 4")
+
+
+
+ +
+
+

Interface tools

+
+
+connect_1to1_families(dist_tree, families, comm, periodic=None, **options)
+

Find the matching faces between cgns nodes belonging to the two provided families.

+

For each one of the two families, all the BC_t or GridConnectivity_t nodes related to the family +through a FamilyName/AdditionalFamilyName node will be included in the pairing process. +These subset must have a Vertex or FaceCenter GridLocation.

+

If the interface is periodic, the transformation from the first to the second family +entities must be specified using the periodic argument; a dictionnary with keys +'translation', 'rotation_center' and/or 'rotation_angle' is expected. +Each key maps to a 3-sized numpy array, with missing keys defaulting zero vector.

+

Input tree is modified inplace : relevant GridConnectivity_t with PointList and PointListDonor +data are created. +If all the original elements are successfully paired, the original nodes are removed. Otherwise, +unmatched faces remains in their original node which is suffixed by ‘_unmatched’.

+

This function allows the additional optional parameters:

+
    +
  • location (default = ‘FaceCenter’) – Controls the output GridLocation of +the created interfaces. ‘FaceCenter’ or ‘Vertex’ are admitted.

  • +
  • tol (default = 1e-2) – Geometric tolerance used to pair two points. Note that for each vertex, this +tolerance is relative to the minimal distance to its neighbouring vertices.

  • +
+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input distributed tree. Only U-NGon connectivities are managed.

  • +
  • families (tuple of str) – Name of the two families to connect.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • periodic (dic, optional) – Transformation from first to second family if the interface is periodic. +None otherwise. Defaults to None.

  • +
  • **options – Additional options

  • +
+
+
+

Example

+
from mpi4py import MPI
+from numpy  import array, pi
+import maia
+import maia.pytree as PT
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+
+# Remove data that should be created
+PT.rm_nodes_from_name(dist_tree, 'PointListDonor')
+PT.rm_nodes_from_name(dist_tree, 'GridConnectivityProperty')
+
+# Create FamilyName on interface nodes
+PT.new_node('FamilyName', 'FamilyName_t', 'Side1', 
+        parent=PT.get_node_from_name(dist_tree, 'matchA'))
+PT.new_node('FamilyName', 'FamilyName_t', 'Side2', 
+        parent=PT.get_node_from_name(dist_tree, 'matchB'))
+
+maia.algo.dist.connect_1to1_families(dist_tree, ('Side1', 'Side2'), MPI.COMM_WORLD,
+        periodic={'rotation_angle' : array([-2*pi/45.,0.,0.])})
+
+assert len(PT.get_nodes_from_name(dist_tree, 'PointListDonor')) == 2
+
+
+
+ +
+
+

Data management

+
+
+redistribute_tree(dist_tree, policy, comm)
+

Redistribute the data of the input tree according to the choosen distribution policy.

+

Supported policies are:

+
    +
  • uniform : each data array is equally reparted over all the processes

  • +
  • gather.N : all the data are moved on process N

  • +
  • gather : shortcut for gather.0

  • +
+

In both case, tree structure remains unchanged on all the processes +(the tree is still a valid distributed tree). +Input is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • policy (str) – distribution policy (see above)

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree_ini = maia.factory.generate_dist_block(21, 'Poly', MPI.COMM_WORLD)
+dist_tree_gathered = maia.algo.dist.redistribute_tree(dist_tree_ini, \
+    'gather.0', MPI.COMM_WORLD)
+
+
+
+ +
+
+
+

Partitioned algorithms

+

The following algorithms applies on maia partitioned trees.

+
+

Geometric calculations

+
+
+compute_cell_center(zone)
+

Compute the cell centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node. +Centers are computed using a basic average over the vertices of the cells.

+
+
Parameters
+

zone (CGNSTree) – Partitionned CGNS Zone

+
+
Returns
+

cell_center (array) – Flat (interlaced) numpy array of cell centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  cell_center = maia.algo.part.compute_cell_center(zone)
+
+
+
+ +
+
+compute_face_center(zone)
+

Compute the face centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node.

+

Centers are computed using a basic average over the vertices of the faces.

+
+

Note

+

If zone is described with standard elements, centers will be computed for elements +explicitly defined in cgns tree.

+
+
+
Parameters
+

zone (CGNSTree) – Partitionned 2D or 3D U CGNS Zone

+
+
Returns
+

face_center (array) – Flat (interlaced) numpy array of face centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  face_center = maia.algo.part.compute_face_center(zone)
+
+
+
+ +
+
+compute_edge_center(zone)
+

Compute the edge centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node, and a unstructured standard elements connectivity.

+
+

Note

+

If zone is described with standard elements, centers will be computed for elements +explicitly defined in cgns tree.

+
+
+
Parameters
+

zone (CGNSTree) – Partitionned 2D or 3D U-elts CGNS Zone

+
+
Returns
+

face_center (array) – Flat (interlaced) numpy array of edge centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, "QUAD_4",  MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  edge_center = maia.algo.part.geometry.compute_edge_center(zone)
+
+
+
+ +
+
+compute_wall_distance(part_tree, comm, point_cloud='CellCenter', out_fs_name='WallDistance', **options)
+

Compute wall distances and add it in tree.

+

For each volumic point, compute the distance to the nearest face belonging to a BC of kind wall. +Computation can be done using “cloud” or “propagation” method.

+
+

Note

+

Propagation method requires ParaDiGMa access and is only available for unstructured cell centered +NGon connectivities grids. In addition, partitions must have been created from a single initial domain +with this method.

+
+

Tree is modified inplace: computed distance are added in a FlowSolution container whose +name can be specified with out_fs_name parameter.

+

The following optional parameters can be used to control the underlying method:

+
+
    +
  • method ({‘cloud’, ‘propagation’}): Choice of the geometric method. Defaults to 'cloud'.

  • +
  • perio (bool): Take into account periodic connectivities. Defaults to True. +Only available when method=cloud.

  • +
+
+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Input partitionned tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • point_cloud (str, optional) – Points to project on the surface. Can either be one of +“CellCenter” or “Vertex” (coordinates are retrieved from the mesh) or the name of a FlowSolution +node in which coordinates are stored. Defaults to CellCenter.

  • +
  • out_fs_name (str, optional) – Name of the output FlowSolution_t node storing wall distance data.

  • +
  • **options – Additional options related to geometric method (see above)

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD)
+assert maia.pytree.get_node_from_name(part_tree, "WallDistance") is not None
+
+
+
+ +
+
+localize_points(src_tree, tgt_tree, location, comm, **options)
+

Localize points between two partitioned trees.

+

For all the points of the target tree matching the given location, +search the cell of the source tree in which it is enclosed. +The result, i.e. the gnum & domain number of the source cell (or -1 if the point is not localized), +are stored in a DiscreteData_t container called “Localization” on the target zones.

+

Source tree must be unstructured and have a NGon connectivity.

+

Localization can be parametred thought the options kwargs:

+
    +
  • loc_tolerance (default = 1E-6) – Geometric tolerance for the method.

  • +
+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned. Only U-NGon connectivities are managed.

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned. Structured or U-NGon connectivities are managed.

  • +
  • location ({'CellCenter', 'Vertex'}) – Target points to localize

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • **options – Additional options related to location strategy

  • +
+
+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt):
+  maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3])
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+
+maia.algo.part.localize_points(part_tree_src, part_tree_tgt, 'CellCenter', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt):
+  loc_container = PT.get_child_from_name(tgt_zone, 'Localization')
+  assert PT.Subset.GridLocation(loc_container) == 'CellCenter'
+
+
+
+ +
+
+find_closest_points(src_tree, tgt_tree, location, comm)
+

Find the closest points between two partitioned trees.

+

For all points of the target tree matching the given location, +search the closest point of same location in the source tree. +The result, i.e. the gnum & domain number of the source point, are stored in a DiscreteData_t +container called “ClosestPoint” on the target zones. +The ids of source points refers to cells or vertices depending on the chosen location.

+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned

  • +
  • location ({'CellCenter', 'Vertex'}) – Entity to use to compute closest points

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt):
+  maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3])
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+
+maia.algo.part.find_closest_points(part_tree_src, part_tree_tgt, 'Vertex', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt):
+  loc_container = PT.get_child_from_name(tgt_zone, 'ClosestPoint')
+  assert PT.Subset.GridLocation(loc_container) == 'Vertex'
+
+
+
+ +
+
+

Mesh extractions

+
+
+iso_surface(part_tree, iso_field, comm, iso_val=0.0, containers_name=[], **options)
+

Create an isosurface from the provided field and value on the input partitioned tree.

+

Isosurface is returned as an independant (2d) partitioned CGNSTree.

+
+

Important

+
    +
  • Input tree must be unstructured and have a ngon connectivity.

  • +
  • Input tree must have been partitioned with preserve_orientation=True partitioning option.

  • +
  • Input field for isosurface computation must be located at vertices.

  • +
  • This function requires ParaDiGMa access.

  • +
+
+
+

Note

+
    +
  • Once created, additional fields can be exchanged from volumic tree to isosurface tree using +_exchange_field(part_tree, iso_part_tree, containers_name, comm).

  • +
  • If elt_type is set to ‘TRI_3’, boundaries from volumic mesh are extracted as edges on +the isosurface (GridConnectivity_t nodes become BC_t nodes) and FaceCenter fields are allowed to be exchanged.

  • +
+
+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree on which isosurf is computed. Only U-NGon +connectivities are managed.

  • +
  • iso_field (str) – Path (starting at Zone_t level) of the field to use to compute isosurface.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • iso_val (float, optional) – Value to use to compute isosurface. Defaults to 0.

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output isosurface tree.

  • +
  • **options – Options related to plane extraction.

  • +
+
+
Returns
+

isosurf_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Isosurface can be controled thought the optional kwargs:

+
+
    +
  • elt_type (str) – Controls the shape of elements used to describe +the isosurface. Admissible values are TRI_3, QUAD_4, NGON_n. Defaults to TRI_3.

  • +
  • graph_part_tool (str) – Controls the isosurface partitioning tool. +Admissible values are hilbert, parmetis, ptscotch. +hilbert may produce unbalanced partitions for some configurations. Defaults to ptscotch.

  • +
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex')
+
+part_tree_iso = maia.algo.part.iso_surface(part_tree, "WallDistance/TurbulentDistance", iso_val=0.25,\
+    containers_name=['WallDistance'], comm=MPI.COMM_WORLD)
+
+assert maia.pytree.get_node_from_name(part_tree_iso, "WallDistance") is not None
+
+
+
+ +
+
+plane_slice(part_tree, plane_eq, comm, containers_name=[], **options)
+

Create a slice from the provided plane equation \(ax + by + cz - d = 0\) +on the input partitioned tree.

+

Slice is returned as an independant (2d) partitioned CGNSTree. See iso_surface() +for use restrictions and additional advices.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree to slice. Only U-NGon connectivities are managed.

  • +
  • sphere_eq (list of float) – List of 4 floats \([a,b,c,d]\) defining the plane equation.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output slice tree.

  • +
  • **options – Options related to plane extraction (see iso_surface()).

  • +
+
+
Returns
+

slice_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD) # Isosurf requires single block mesh
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0.5], MPI.COMM_WORLD, elt_type='QUAD_4')
+
+
+
+ +
+
+spherical_slice(part_tree, sphere_eq, comm, containers_name=[], **options)
+

Create a spherical slice from the provided equation +\((x-x_0)^2 + (y-y_0)^2 + (z-z_0)^2 = R^2\) +on the input partitioned tree.

+

Slice is returned as an independant (2d) partitioned CGNSTree. See iso_surface() +for use restrictions and additional advices.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree to slice. Only U-NGon connectivities are managed.

  • +
  • plane_eq (list of float) – List of 4 floats \([x_0, y_0, z_0, R]\) defining the sphere equation.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output slice tree.

  • +
  • **options – Options related to plane extraction (see iso_surface()).

  • +
+
+
Returns
+

slice_tree (CGNSTree) – Surfacic tree (partitioned)

+
+
+

Example

+
from mpi4py import MPI
+import numpy
+import maia
+import maia.pytree as PT
+dist_tree = maia.factory.generate_dist_block(11, 'Poly', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD, preserve_orientation=True)
+
+# Add solution
+zone      = PT.get_node_from_label(part_tree, "Zone_t")
+vol_rank  = MPI.COMM_WORLD.Get_rank() * numpy.ones(PT.Zone.n_cell(zone))
+src_sol   = PT.new_FlowSolution('FlowSolution', loc='CellCenter', fields={'i_rank' : vol_rank}, parent=zone)
+
+slice_tree = maia.algo.part.spherical_slice(part_tree, [0.5,0.5,0.5,0.25], MPI.COMM_WORLD, \
+    ["FlowSolution"], elt_type="NGON_n")
+
+assert maia.pytree.get_node_from_name(slice_tree, "FlowSolution") is not None
+
+
+
+ +
+
+extract_part_from_zsr(part_tree, zsr_name, comm, containers_name=[], **options)
+

Extract the submesh defined by the provided ZoneSubRegion from the input volumic +partitioned tree.

+

Dimension of the output mesh is set up accordingly to the GridLocation of the ZoneSubRegion. +Submesh is returned as an independant partitioned CGNSTree and includes the relevant connectivities.

+

In addition, containers specified in containers_name list are transfered to the extracted tree. +Containers to be transfered can be either of label FlowSolution_t or ZoneSubRegion_t.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree from which extraction is computed. Only U-NGon +connectivities are managed.

  • +
  • zsr_name (str) – Name of the ZoneSubRegion_t node

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the fields containers to transfer +on the output extracted tree.

  • +
  • **options – Options related to the extraction.

  • +
+
+
Returns
+

extracted_tree (CGNSTree) – Extracted submesh (partitioned)

+
+
+

Extraction can be controled by the optional kwargs:

+
+
    +
  • graph_part_tool (str) – Partitioning tool used to balance the extracted zones. +Admissible values are hilbert, parmetis, ptscotch. Note that +vertex-located extractions require hilbert partitioning. Defaults to hilbert.

  • +
+
+
+

Important

+
    +
  • Input tree must be unstructured and have a ngon connectivity.

  • +
  • Partitions must come from a single initial domain on input tree.

  • +
+
+
+

See also

+

create_extractor_from_zsr() takes the same parameters, excepted containers_name, +and returns an Extractor object which can be used to exchange containers more than once through its +Extractor.exchange_fields(container_name) method.

+
+

Example

+
from   mpi4py import MPI
+import numpy as np
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex')
+
+# Create a ZoneSubRegion on procs for extracting odd cells
+for part_zone in PT.get_all_Zone_t(part_tree):
+  ncell       = PT.Zone.n_cell(part_zone)
+  start_range = PT.Element.Range(PT.Zone.NFaceNode(part_zone))[0]
+  point_list  = np.arange(start_range, start_range+ncell, 2, dtype=np.int32).reshape((1,-1), order='F')
+  PT.new_ZoneSubRegion(name='ZoneSubRegion', point_list=point_list, loc='CellCenter', parent=part_zone)
+
+extracted_tree = maia.algo.part.extract_part_from_zsr(part_tree, 'ZoneSubRegion', MPI.COMM_WORLD,
+                                                      containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_tree, "WallDistance") is not None
+
+
+
+ +
+
+extract_part_from_bc_name(part_tree, bc_name, comm, transfer_dataset=True, containers_name=[], **options)
+

Extract the submesh defined by the provided BC name from the input volumic +partitioned tree.

+

Behaviour and arguments of this function are similar to those of extract_part_from_zsr() +(zsr_name becomes bc_name). Optional transfer_dataset argument allows to +transfer BCDataSet from BC to the extracted mesh (default to True).

+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex')
+
+extracted_bc = maia.algo.part.extract_part_from_bc_name(part_tree, \
+               'wall', MPI.COMM_WORLD, containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_bc, "WallDistance") is not None
+
+
+
+ +
+
+

Interpolations

+
+
+interpolate_from_part_trees(src_tree, tgt_tree, comm, containers_name, location, **options)
+

Interpolate fields between two partitionned trees.

+

For now, interpolation is limited to lowest order: target points take the value of the +closest point (or their englobing cell, depending of choosed options) in the source mesh. +Interpolation strategy can be controled thought the options kwargs:

+
    +
  • strategy (default = ‘Closest’) – control interpolation method

    +
      +
    • ‘Closest’ : Target points take the value of the closest source cell center.

    • +
    • ‘Location’ : Target points take the value of the cell in which they are located. +Unlocated points have take a NaN value.

    • +
    • ‘LocationAndClosest’ : Use ‘Location’ method and then ‘ClosestPoint’ method +for the unlocated points.

    • +
    +
  • +
  • loc_tolerance (default = 1E-6) – Geometric tolerance for Location method.

  • +
+
+

Important

+
    +
  • Source fields must be located at CellCenter.

  • +
  • Source tree must be unstructured and have a ngon connectivity.

  • +
+
+
+

See also

+

create_interpolator_from_part_trees() takes the same parameters, excepted containers_name, +and returns an Interpolator object which can be used to exchange containers more than once through its +Interpolator.exchange_fields(container_name) method.

+
+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned. Only U-NGon connectivities are managed.

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned. Structured or U-NGon connectivities are managed.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the source FlowSolution_t nodes to transfer.

  • +
  • location ({'CellCenter', 'Vertex'}) – Expected target location of the fields.

  • +
  • **options – Options related to interpolation strategy

  • +
+
+
+

Example

+
import mpi4py
+import numpy
+import maia
+import maia.pytree as PT
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.factory.generate_dist_block(11, 'Poly', comm)
+dist_tree_tgt = maia.factory.generate_dist_block(20, 'Poly', comm)
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+# Create fake solution
+zone = maia.pytree.get_node_from_label(part_tree_src, "Zone_t")
+src_sol = maia.pytree.new_FlowSolution('FlowSolution', loc='CellCenter', parent=zone)
+PT.new_DataArray("Field", numpy.random.rand(PT.Zone.n_cell(zone)), parent=src_sol)
+
+maia.algo.part.interpolate_from_part_trees(part_tree_src, part_tree_tgt, comm,\
+    ['FlowSolution'], 'Vertex')
+tgt_sol = PT.get_node_from_name(part_tree_tgt, 'FlowSolution')
+assert tgt_sol is not None and PT.Subset.GridLocation(tgt_sol) == 'Vertex'
+
+
+
+ +
+
+centers_to_nodes(tree, comm, containers_name=[], **options)
+

Create Vertex located FlowSolution_t from CellCenter located FlowSolution_t.

+

Interpolation is based on Inverse Distance Weighting +(IDW) method: +each cell contributes to each of its vertices with a weight computed from the distance +between the cell isobarycenter and the vertice. The method can be tuned with +the following kwargs:

+
    +
  • idw_power (float, default = 1) – Power to which the cell-vertex distance is elevated.

  • +
  • cross_domain (bool, default = True) – If True, vertices located at domain +interfaces also receive data from the opposite domain cells. This parameter does not +apply to internal partitioning interfaces, which are always crossed.

  • +
+
+
Parameters
+
    +
  • tree (CGNSTree) – Partionned tree. Only unstructured connectivities are managed.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer.

  • +
  • **options – Options related to interpolation, see above.

  • +
+
+
+
+

See also

+

A CenterToNode object can be instanciated with the same parameters, excluding containers_name, +and then be used to move containers more than once with its +move_fields(container_name) method.

+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Init a FlowSolution located at Cells
+for part in PT.get_all_Zone_t(part_tree):
+  cell_center = maia.algo.part.compute_cell_center(part)
+  fields = {'ccX': cell_center[0::3], 'ccY': cell_center[1::3], 'ccZ': cell_center[2::3]}
+  PT.new_FlowSolution('FSol', loc='CellCenter', fields=fields, parent=part)
+
+maia.algo.part.centers_to_nodes(part_tree, comm, ['FSol'])
+
+for part in PT.get_all_Zone_t(part_tree):
+  vtx_sol = PT.get_node_from_name(part, 'FSol#Vtx')
+  assert PT.Subset.GridLocation(vtx_sol) == 'Vertex'
+
+
+
+ +
+
+
+

Generic algorithms

+

The following algorithms applies on maia distributed or partitioned trees

+
+
+transform_affine(t, rotation_center=array([0., 0., 0.]), rotation_angle=array([0., 0., 0.]), translation=array([0., 0., 0.]), apply_to_fields=False)
+

Apply the affine transformation to the coordinates of the given zone.

+

Input zone(s) can be either structured or unstructured, but must have cartesian coordinates. +Transformation is defined by

+
+\[\tilde v = R \cdot (v - c) + c + t\]
+

where c, t are the rotation center and translation vector and R is the rotation matrix.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher.

  • +
  • rotation_center (array) – center coordinates of the rotation

  • +
  • rotation_angler (array) – angles of the rotation

  • +
  • translation (array) – translation vector components

  • +
  • apply_to_fields (bool, optional) – if True, apply the rotation vector to the vectorial fields found under +following nodes : FlowSolution_t, DiscreteData_t, ZoneSubRegion_t, BCDataset_t. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+zone = maia.pytree.get_all_Zone_t(dist_tree)[0]
+
+maia.algo.transform_affine(zone, translation=[3,0,0])
+
+
+
+ +
+
+pe_to_nface(t, comm=None, removePE=False)
+

Create a NFace node from a NGon node with ParentElements.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Distributed or Partitioned tree (or sequences of) +starting at Zone_t level or higher.

  • +
  • comm (MPIComm) – MPI communicator, mandatory only for distributed zones

  • +
  • remove_PE (bool, optional) – If True, remove the ParentElements node. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+tree = maia.factory.generate_dist_block(6, 'Poly', MPI.COMM_WORLD)
+
+for zone in maia.pytree.get_all_Zone_t(tree):
+  maia.algo.pe_to_nface(zone, MPI.COMM_WORLD)
+  assert maia.pytree.get_child_from_name(zone, 'NFaceElements') is not None
+
+
+
+ +
+
+nface_to_pe(t, comm=None, removeNFace=False)
+

Create a ParentElements node in the NGon node from a NFace node.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Distributed or Partitioned tree (or sequences of) +starting at Zone_t level or higher.

  • +
  • comm (MPIComm) – MPI communicator, mandatory only for distributed zones

  • +
  • removeNFace (bool, optional) – If True, remove the NFace node. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+tree = maia.factory.generate_dist_block(6, 'NFace_n', MPI.COMM_WORLD)
+
+maia.algo.nface_to_pe(tree, MPI.COMM_WORLD)
+assert maia.pytree.get_node_from_name(tree, 'ParentElements') is not None
+
+
+
+ +
+
+

Sequential algorithms

+

The following algorithms applies on regular pytrees.

+
+
+poly_new_to_old(tree, full_onera_compatibility=True)
+

Transform a tree with polyhedral unstructured connectivity with new CGNS 4.x conventions to old CGNS 3.x conventions.

+

The tree is modified in place.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree described with new CGNS convention.

  • +
  • full_onera_compatibility (bool) – if True, shift NFace and ParentElements ids to begin at 1, irrespective of the NGon and NFace ElementRanges, and make the NFace connectivity unsigned

  • +
+
+
+
+ +
+
+poly_old_to_new(tree)
+

Transform a tree with polyhedral unstructured connectivity with old CGNS 3.x conventions to new CGNS 4.x conventions.

+

The tree is modified in place.

+

This function accepts trees with old ONERA conventions where NFace and ParentElements ids begin at 1, irrespective of the NGon and NFace ElementRanges, and where the NFace connectivity is unsigned. The resulting tree has the correct CGNS/SIDS conventions.

+
+
Parameters
+

tree (CGNSTree) – Tree described with old CGNS convention.

+
+
+
+ +
+
+enforce_ngon_pe_local(t)
+

Shift the ParentElements values in order to make it start at 1, as requested by legacy tools.

+

The tree is modified in place.

+
+
Parameters
+

t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher.

+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/user_manual/config.html b/docs/1.2/user_manual/config.html new file mode 100644 index 00000000..fec28ef1 --- /dev/null +++ b/docs/1.2/user_manual/config.html @@ -0,0 +1,334 @@ + + + + + + + + + + Configuration — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Configuration

+
+

Logging

+

Maia provides informations to the user through the loggers summarized +in the following table:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +

Logger

Purpose

Default printer

maia

Light general info

mpi_rank_0_stdout_printer

maia-warnings

Warnings

mpi_rank_0_stdout_printer

maia-errors

Errors

mpi_rank_0_stdout_printer

maia-stats

More detailed timings +and memory usage

No output

+

The easiest way to change this default configuration is to +set the environment variable LOGGING_CONF_FILE to provide a file +(e.g. logging.conf) looking like this:

+
maia          : mpi_stdout_printer        # All ranks output to sdtout
+maia-warnings :                           # No output
+maia-errors   : mpi_rank_0_stderr_printer # Rank 0 output to stderr
+maia-stats    : file_printer("stats.log") # All ranks output in the file
+
+
+

See Log management for a full description of available +printers or for instructions on how to create your own printers. +This page also explain how to dynamically change printers +directly in your Python application.

+
+
+

Exception handling

+

Maia automatically override the sys.excepthook +function to call MPI_Abort when an uncatched exception occurs. +This allow us to terminate the MPI execution and to avoid some deadlocks +if an exception is raised by a single process. +Note that this call force the global COMM_WORLD communicator to abort which +can have undesired effects if sub communicators are used.

+

This behaviour can be disabled with a call to +maia.excepthook.disable_mpi_excepthook().

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/user_manual/factory.html b/docs/1.2/user_manual/factory.html new file mode 100644 index 00000000..7dfb6c7c --- /dev/null +++ b/docs/1.2/user_manual/factory.html @@ -0,0 +1,856 @@ + + + + + + + + + + Factory module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Factory module

+

The maia.factory module can be used to generate parallel CGNS trees. Those can +be obtained from an other kind of tree, or can be generated from user parameters.

+
+

Generation

+

Generation functions create a distributed tree from some parameters.

+
+
+generate_dist_points(n_vtx, zone_type, comm, origin=array([0., 0., 0.]), max_coords=array([1., 1., 1.]))
+

Generate a distributed mesh including only cartesian points.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +of the zone is controled by the zone_type parameter:

+
    +
  • "Structured" (or "S") produces a structured zone

  • +
  • "Unstructured" (or "U") produces an unstructured zone

  • +
+

In all cases, the created zone contains only the cartesian grid coordinates; no connectivities are created. +The physical dimension of the output +is set equal to the length of the origin parameter.

+
+
Parameters
+
    +
  • n_vtx (int or array of int) – Number of vertices in each direction. Scalars +automatically extend to uniform array.

  • +
  • zone_type (str) – requested kind of points cloud

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • max_coords (array, optional) – Coordinates of the higher point of the generated mesh. Defaults to ones vector.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_points(10, 'Unstructured', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Zone.n_vtx(zone) == 10**3
+
+
+
+ +
+
+generate_dist_block(n_vtx, cgns_elmt_name, comm, origin=array([0., 0., 0.]), edge_length=1.0)
+

Generate a distributed mesh with a cartesian topology.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +and cell dimension of the zone is controled by the cgns_elmt_name parameter:

+
    +
  • "Structured" (or "S") produces a structured zone,

  • +
  • "Poly" produces an unstructured 3d zone with a NGon+PE connectivity,

  • +
  • "NFACE_n" produces an unstructured 3d zone with a NFace+NGon connectivity,

  • +
  • "NGON_n" produces an unstructured 2d zone with faces described by a NGon +node (not yet implemented),

  • +
  • Other names must be in ["TRI_3", "QUAD_4", "TETRA_4", "PYRA_5", "PENTA_6", "HEXA_8"] +and produces an unstructured 2d or 3d zone with corresponding standard elements.

  • +
+

In all cases, the created zone contains the cartesian grid coordinates and the relevant number +of boundary conditions.

+

When creating 2 dimensional zones, the +physical dimension +is set equal to the length of the origin parameter.

+
+
Parameters
+
    +
  • n_vtx (int or array of int) – Number of vertices in each direction. Scalars +automatically extend to uniform array.

  • +
  • cgns_elmt_name (str) – requested kind of elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • edge_length (float, optional) – Edge size of the generated mesh. Defaults to 1.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_block([10,20,10], 'Structured', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Zone.Type(zone) == "Structured"
+
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Element.CGNSName(PT.get_child_from_label(zone, 'Elements_t')) == 'NGON_n'
+
+dist_tree = maia.factory.generate_dist_block(10, 'TETRA_4', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Element.CGNSName(PT.get_child_from_label(zone, 'Elements_t')) == 'TETRA_4'
+
+
+
+ +
+
+generate_dist_sphere(m, cgns_elmt_name, comm, origin=array([0., 0., 0.]), radius=1.0)
+

Generate a distributed mesh with a spherical topology.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +and cell dimension of the zone is controled by the cgns_elmt_name parameter:

+
    +
  • "NFACE_n" produces an unstructured 3d zone with a NFace+NGon connectivity,

  • +
  • "NGON_n" produces an unstructured 2d zone with a NGon+Bar connectivity,

  • +
  • Other names must be in ["TRI_3", "TETRA_4"] +and produces an unstructured 2d or 3d zone with corresponding standard elements.

  • +
+

In all cases, the created zone contains the grid coordinates and the relevant number +of boundary conditions.

+

Spherical meshes are +class I geodesic polyhedra +(icosahedral). Number of vertices on the external surface is equal to +\(10m^2+2\).

+
+
Parameters
+
    +
  • m (int) – Strict. positive integer who controls the number of vertices (see above)

  • +
  • cgns_elmt_name (str) – requested kind of elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • radius (float, optional) – Radius of the generated sphere. Defaults to 1.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_sphere(10, 'TRI_3', MPI.COMM_WORLD)
+assert PT.Element.CGNSName(PT.get_node_from_label(dist_tree, 'Elements_t')) == 'TRI_3'
+
+
+
+ +
+
+

Partitioning

+
+
+partition_dist_tree(dist_tree, comm, **kwargs)
+

Perform the partitioning operation: create a partitioned tree from the input distributed tree.

+

The input tree can be structured or unstuctured, but hybrid meshes are not yet supported.

+
+

Important

+

Geometric information (such as boundary conditions, zone subregion, etc.) are reported +on the partitioned tree; however, data fields (BCDataSet, FlowSolution, etc.) are not +transfered automatically. See maia.transfer module.

+
+

See reference documentation for the description of the keyword arguments.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • **kwargs – Partitioning options

  • +
+
+
Returns
+

CGNSTree – partitioned cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+comm = MPI.COMM_WORLD
+i_rank, n_rank = comm.Get_rank(), comm.Get_size()
+dist_tree  = maia.factory.generate_dist_block(10, 'Poly', comm)
+
+#Basic use
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+#Crazy partitioning where each proc get as many partitions as its rank
+n_part_tot = n_rank * (n_rank + 1) // 2
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm, \
+    zone_to_parts={'Base/zone' : [1./n_part_tot for i in range(i_rank+1)]})
+assert len(maia.pytree.get_all_Zone_t(part_tree)) == i_rank+1
+
+
+
+ +
+

Partitioning options

+

Partitioning can be customized with the following keywords arguments:

+
+
+graph_part_tool
+

Method used to split unstructured blocks. Irrelevent for structured blocks.

+
+
Admissible values
+
    +
  • parmetis, ptscotch : graph partitioning methods,

  • +
  • hilbert : geometric method (only for NGon connectivities),

  • +
  • gnum : cells are dispatched according to their absolute numbering.

  • +
+
+
Default value
+

parmetis, if installed; else ptscotch, if installed; hilbert otherwise.

+
+
+
+ +
+
+zone_to_parts
+

Control the number, size and repartition of partitions. See Repartition.

+
+
Default value
+

Computed such that partitioning is balanced using +maia.factory.partitioning.compute_balanced_weights().

+
+
+
+ +
+
+part_interface_loc
+

GridLocation for the created partitions interfaces. Pre-existing interface keep their original location.

+
+
Admissible values
+

FaceCenter, Vertex

+
+
Default value
+

FaceCenter for unstructured zones with NGon connectivities; Vertex otherwise.

+
+
+
+ +
+
+reordering
+

Dictionnary of reordering options, which are used to renumber the entities in each partitioned zone. See +corresponding documentation.

+
+ +
+
+preserve_orientation
+

If True, the created interface faces are not reversed and keep their original orientation. Consequently, +NGonElements can have a zero left parent and a non zero right parent. +Only relevant for U/NGon partitions.

+
+
Default value
+

False

+
+
+
+ +
+
+dump_pdm_output
+

If True, dump the raw arrays created by paradigm in a CGNSNode at (partitioned) zone level. For debug only.

+
+
Default value
+

False

+
+
+
+ +
+
+

Repartition

+

The number, size, and repartition (over the processes) of the created partitions is +controlled through the zone_to_parts keyword argument: each process must provide a +dictionary associating the path of every distributed zone to a list of floats. +For a given distributed zone, current process will receive as many partitions +as the length of the list (missing path or empty list means no partitions); the size of these partitions +corresponds to the values in the list (expressed in fraction of the input zone). +For a given distributed zone, the sum of all the fractions across all the processes must +be 1.

+

This dictionary can be created by hand; for convenience, Maia provides three functions in the +maia.factory.partitioning module to create this dictionary.

+
+
+compute_regular_weights(tree, comm, n_part=1)
+

Compute a basic zone_to_parts repartition.

+

Each process request n_part partitions on each original zone (n_part can differ +for each proc). +The weights of all the parts produced from a given zone are homogeneous +and equal to the number of cells in the zone divided by the total +number of partitions requested for this zone.

+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
  • n_part (int,optional) – Number of partitions to produce on each zone by the proc. +Defaults to 1.

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+zone_to_parts = mpart.compute_regular_weights(dist_tree, MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_size() == 2:
+  assert zone_to_parts == {'Base/Large': [0.5], 'Base/Small': [0.5]}
+
+
+
+ +
+
+compute_balanced_weights(tree, comm, only_uniform=False)
+

Compute a well balanced zone_to_parts repartition.

+

Each process request or not partitions with heterogeneous weight on each +original zone such that:

+
    +
  • the computational load is well balanced, ie the total number of +cells per process is nearly equal,

  • +
  • the number of splits within a given zone is minimized,

  • +
  • produced partitions are not too small.

  • +
+
+

Note

+

Heterogeneous weights are not managed by ptscotch. Use parmetis as graph_part_tool +for partitioning if repartition was computed with this function, or set optional +argument only_uniform to True.

+
+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
  • only_uniform (bool, optional) – If true, an alternative balancing method is used +in order to request homogeneous weights, but load balance is less equilibrated. +Default to False.

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+comm = MPI.COMM_WORLD
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', comm)
+
+zone_to_parts = mpart.compute_balanced_weights(dist_tree, comm)
+if comm.Get_size() == 2 and comm.Get_rank() == 0:
+  assert zone_to_parts == {'Base/Large': [0.375], 'Base/Small': [1.0]}
+if comm.Get_size() == 2 and comm.Get_rank() == 1:
+  assert zone_to_parts == {'Base/Large': [0.625]}
+
+
+
+ +
+
+compute_nosplit_weights(tree, comm)
+

Compute a zone_to_parts repartition without splitting the blocks.

+

The initial blocks will be simply distributed over the available processes, +minimizing the total number of cells affected to a proc. This leads to a poor load +balancing and possibly to procs having no partitions at all.

+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+zone_to_parts = mpart.compute_nosplit_weights(dist_tree, MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_size() == 2:
+  assert len(zone_to_parts) == 1
+
+
+
+ +
+
+

Reordering options

+

For unstructured zones, the reordering options are transmitted to ParaDiGM in order to +control the renumbering of mesh entities in the partitions.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Kwarg

Admissible values

Effect

Default

cell_renum_method

“NONE”, “RANDOM”, “HILBERT”, +“CUTHILL”, “CACHEBLOCKING”, +“CACHEBLOCKING2”, “HPC”

Renumbering method for the cells

NONE

face_renum_method

“NONE”, “RANDOM”, “LEXICOGRAPHIC”

Renumbering method for the faces

NONE

vtx_renum_method

“NONE”, “SORT_INT_EXT”

Renumbering method for the vertices

NONE

n_cell_per_cache

Integer >= 0

Specific to cacheblocking

0

n_face_per_pack

Integer >= 0

Specific to cacheblocking

0

graph_part_tool

“parmetis”, “ptscotch”, +“hyperplane”

Graph partitioning library to +use for renumbering

Same as partitioning tool

+
+
+

Recovering from partitions

+
+
+recover_dist_tree(part_tree, comm)
+

Regenerate a distributed tree from a partitioned tree.

+

The partitioned tree should have been created using Maia, or +must at least contains GlobalNumbering nodes as defined by Maia +(see Partitioned trees).

+

The following nodes are managed : GridCoordinates, Elements, ZoneBC, ZoneGridConnectivity +FlowSolution, DiscreteData and ZoneSubRegion.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned CGNS Tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+comm = MPI.COMM_WORLD
+
+dist_tree_bck  = maia.factory.generate_dist_block(5, 'TETRA_4', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree_bck, comm)
+
+dist_tree = maia.factory.recover_dist_tree(part_tree, comm)
+assert maia.pytree.is_same_tree(dist_tree, dist_tree_bck)
+
+
+
+ +
+
+
+

Managing full trees

+

Almost no function in maia supports full (not distributed) CGNS trees, +but they can be useful for compatibility with sequential libraries.

+
+
+full_to_dist_tree(tree, comm, owner=None)
+

Generate a distributed tree from a standard (full) CGNS Tree.

+

Input tree can be defined on a single process (using owner = rank_id), +or a copy can be known by all the processes (using owner=None).

+

In both cases, output distributed tree will be equilibrated over all the processes.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Full (not distributed) tree.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • owner (int, optional) – MPI rank holding the input tree. Defaults to None.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+comm = MPI.COMM_WORLD
+
+if comm.Get_rank() == 0:
+  tree = maia.io.read_tree(mesh_dir/'S_twoblocks.yaml', comm)
+else:
+  tree = None
+dist_tree = maia.factory.full_to_dist_tree(tree, comm, owner=0)
+
+
+
+ +
+
+dist_to_full_tree(dist_tree, comm, target=0)
+

Generate a standard (full) CGNS Tree from a distributed tree.

+

The output tree can be used with sequential tools, but is no more compatible with +maia parallel algorithms.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed CGNS tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • target (int, optional) – MPI rank holding the output tree. Defaults to 0.

  • +
+
+
Returns
+

CGNSTree – Full (not distributed) tree or None

+
+
+

Example

+
from   mpi4py import MPI
+import maia
+comm = MPI.COMM_WORLD
+
+dist_tree = maia.factory.generate_dist_sphere(10, 'TRI_3', comm)
+full_tree = maia.factory.dist_to_full_tree(dist_tree, comm, target=0)
+
+if comm.Get_rank() == 0:
+  assert maia.pytree.get_node_from_name(full_tree, ":CGNS#Distribution") is None
+else:
+  assert full_tree is None
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/user_manual/io.html b/docs/1.2/user_manual/io.html new file mode 100644 index 00000000..92b9eeac --- /dev/null +++ b/docs/1.2/user_manual/io.html @@ -0,0 +1,519 @@ + + + + + + + + + + File management — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

File management

+

Maia supports HDF5/CGNS file reading and writing, +see related documention.

+

The IO functions are provided by the maia.io module. All the high level functions +accepts a legacy parameter used to control the low level CGNS-to-hdf driver:

+
    +
  • if legacy==False (default), hdf calls are performed by the python module +h5py.

  • +
  • if legacy==True, hdf calls are performed by +Cassiopee.Converter module.

  • +
+

The requested driver should be installed on your computer as well as the +hdf5 library compiled with parallel support.

+
+

Distributed IO

+

Distributed IO is the privileged way to deal with CGNS files within your maia workflows. +Files are loaded as distributed trees, and, inversely, distributed trees can be written +into a single CGNS file.

+

High level IO operations can be performed with the two following functions, which read +or write all data they found :

+
+
+file_to_dist_tree(filename, comm, legacy=False)
+

Distributed load of a CGNS file.

+
+
Parameters
+
    +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
Returns
+

CGNSTree – Distributed CGNS tree

+
+
+
+ +
+
+dist_tree_to_file(dist_tree, filename, comm, legacy=False)
+

Distributed write to a CGNS file.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree to write

  • +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +

The example below shows how to uses these high level functions:

+
from mpi4py import MPI
+import maia
+
+# Generate a sample tree
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+# Write
+maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+# Read
+tree = maia.io.file_to_dist_tree("tree.cgns", MPI.COMM_WORLD)
+
+
+

Finer control of what is written or loaded can be achieved with the following steps:

+
    +
  • For a write operation, the easiest way to write only some nodes in +the file is to remove the unwanted nodes from the distributed tree.

  • +
  • For a read operation, the load has to be divided into the following steps:

    +
      +
    • Loading a size_tree: this tree has only the shape of the distributed data and +not the data itself.

    • +
    • Removing unwanted nodes in the size tree;

    • +
    • Fill the filtered tree from the file.

    • +
    +
  • +
+

The example below illustrate how to filter the written or loaded nodes:

+
from mpi4py import MPI
+import maia
+
+# Generate a sample tree
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+
+# Remove the nodes we do not want to write
+maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateZ') #This is a DataArray
+maia.pytree.rm_nodes_from_name(dist_tree, 'Zm*') #This is some BC nodes
+# Write
+maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+# Read
+from maia.io.cgns_io_tree import load_collective_size_tree, fill_size_tree
+dist_tree = load_collective_size_tree("tree.cgns", MPI.COMM_WORLD)
+#For now dist_tree only contains sizes : let's filter it
+maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateY') #This is a DataArray
+maia.pytree.rm_nodes_from_name(dist_tree, 'Ym*') #This is some BC nodes
+fill_size_tree(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+
+
+
+

Partitioned IO

+

In some cases, it may be useful to write or read a partitioned tree (keeping the +partitioned zones separated). This can be achieved using the following functions:

+
+
+part_tree_to_file(part_tree, filename, comm, single_file=False, legacy=False)
+

Gather the partitioned zones managed by all the processes and write it in a unique +hdf container.

+

If single_file is True, one file named filename storing all the partitioned +zones is written. Otherwise, hdf links are used to produce a main file filename +linking to additional subfiles.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree

  • +
  • filename (str) – Path of the output file

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • single_file (bool) – Produce a unique file if True; use CGNS links otherwise.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.io.part_tree_to_file(part_tree, 'part_tree.cgns', MPI.COMM_WORLD)
+
+
+
+ +
+
+file_to_part_tree(filename, comm, redispatch=False, legacy=False)
+

Read the partitioned zones from a hdf container and affect them +to the ranks.

+

If redispatch == False, the CGNS zones are affected to the +rank indicated in their name. +The size of the MPI communicator must thus be equal to the highest id +appearing in partitioned zone names.

+

If redispatch == True, the CGNS zones are dispatched over the +available processes, and renamed to follow maia’s conventions.

+
+
Parameters
+
    +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • redispatch (bool) – Controls the affectation of the partitions to the available ranks (see above). +Defaults to False.

  • +
+
+
Returns
+

CGNSTree – Partitioned CGNS tree

+
+
+
+ +
+
+

Raw IO

+

For debug purpose, trees can also be read or written independently in a +sequential manner. Be aware that information added by maia such as Distribution +or GlobalNumbering nodes will not be removed.

+
+
+read_tree(filename, legacy=False)
+

Sequential load of a CGNS file.

+
+
Parameters
+

filename (str) – Path of the file

+
+
Returns
+

CGNSTree – Full (not distributed) CGNS tree

+
+
+
+ +
+ +

Detect the links embedded in a CGNS file.

+

Links information are returned as described in sids-to-python. Note that +no data are loaded and the tree structure is not even built.

+
+
Parameters
+

filename (str) – Path of the file

+
+
Returns
+

list – Links description

+
+
+
+ +
+
+write_tree(tree, filename, links=[], legacy=False)
+

Sequential write to a CGNS file.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree to write

  • +
  • filename (str) – Path of the file

  • +
  • links (list) – List of links to create (see SIDS-to-Python guide)

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_rank() == 0:
+  maia.io.write_tree(dist_tree, "tree.cgns")
+
+
+
+ +
+
+write_trees(tree, filename, comm, legacy=False)
+

Sequential write to CGNS files.

+

Write separate trees for each process. Rank id will be automatically +inserted in the filename.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree to write

  • +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+maia.io.write_trees(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/user_manual/transfer.html b/docs/1.2/user_manual/transfer.html new file mode 100644 index 00000000..f37d5aa9 --- /dev/null +++ b/docs/1.2/user_manual/transfer.html @@ -0,0 +1,497 @@ + + + + + + + + + + Transfer module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Transfer module

+

The maia.transfer contains functions that exchange data between the +partitioned and distributed meshes.

+
+

Fields transfer

+

High level APIs allow to exchange data at CGNS Tree or Zone level. +All the provided functions take similar parameters: a distributed tree (resp. zone), +the corresponding partitioned tree (resp. list of zones), the MPI communicator and +optionally a filtering parameter.

+

The following kind of data are supported: +FlowSolution_t, DiscreteData_t, ZoneSubRegion_t and BCDataSet_t.

+

When transferring from distributed meshes to partitioned meshes, fields are supposed +to be known on the source mesh across all the ranks (according to disttree definition). +Geometric patches (such as ZoneSubRegion or BCDataSet) must exists on the relevant partitions, meaning +that only fields are transfered.

+

When transferring from partitioned meshes to distributed meshes, geometric patches may or may not exist on the +distributed zone: they will be created if needed. This allows to transfer fields that have been created +on the partitioned meshes. +It is however assumed that global data (e.g. FlowSolution) are defined on every partition.

+
+

Tree level

+

All the functions of this section operate inplace and require the following parameters:

+
    +
  • dist_tree (CGNSTree) – Distributed CGNS Tree

  • +
  • part_tree (CGNSTree) – Corresponding partitioned CGNS Tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+dist_tree_to_part_tree_all(dist_tree, part_tree, comm)
+

Transfer all the data fields from a distributed tree +to the corresponding partitioned tree.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+dist_tree = maia.io.file_to_dist_tree(filename, MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+maia.transfer.dist_tree_to_part_tree_all(dist_tree, part_tree, MPI.COMM_WORLD)
+
+zone = PT.get_all_Zone_t(part_tree)[0]
+assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_tree_to_dist_tree_all(dist_tree, part_tree, comm)
+

Transfer all the data fields from a partitioned tree +to the corresponding distributed tree.

+
+ +

In addition, the next two methods expect the parameter labels (list of str) +which allow to pick one or more kind of data to transfer from the supported labels.

+
+
+dist_tree_to_part_tree_only_labels(dist_tree, part_tree, labels, comm)
+

Transfer all the data fields of the specified labels from a distributed tree +to the corresponding partitioned tree.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['FlowSolution_t', 'ZoneSubRegion_t'], comm)
+
+zone = PT.get_all_Zone_t(part_tree)[0]
+assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None
+
+maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['BCDataSet_t'], comm)
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_tree_to_dist_tree_only_labels(dist_tree, part_tree, labels, comm)
+

Transfer all the data fields of the specified labels from a partitioned tree +to the corresponding distributed tree.

+
+ +
+
+

Zone level

+

All the functions of this section operate inplace and require the following parameters:

+
    +
  • dist_zone (CGNSTree) – Distributed CGNS Zone

  • +
  • part_zones (list of CGNSTree) – Corresponding partitioned CGNS Zones

  • +
  • comm (MPIComm) – MPI communicator

  • +
+

In addition, filtering is possible with the use of the +include_dict or exclude_dict dictionaries. These dictionaries map +each supported label to a list of cgns paths to include (or exclude). Paths +starts from the Zone_t node and ends at the targeted DataArray_t node. +Wildcard * are allowed in paths : for example, considering the following tree +structure,

+
Zone (Zone_t)
+├── FirstSolution (FlowSolution_t)
+│   ├── Pressure (DataArray_t)
+│   ├── MomentumX (DataArray_t)
+│   └── MomentumY (DataArray_t)
+├── SecondSolution (FlowSolution_t)
+│   ├── Pressure (DataArray_t)
+│   ├── MomentumX (DataArray_t)
+│   └── MomentumY (DataArray_t)
+└── SpecialSolution (FlowSolution_t)
+    ├── Density (DataArray_t)
+    └── MomentumZ (DataArray_t)
+
+
+
+
"FirstSolution/Momentum*" maps to ["FirstSolution/MomentumX", "FirstSolution/MomentumY"],
+
"*/Pressure maps to ["FirstSolution/Pressure", "SecondSolution/Pressure"], and
+
"S*/M*" maps to ["SecondSolution/MomentumX", "SecondSolution/MomentumY", "SpecialSolution/MomentumZ"].
+
+

For convenience, we also provide the magic path ['*'] meaning “everything related to this +label”.

+

Lastly, we use the following rules to manage missing label keys in dictionaries:

+
+
    +
  • For _only functions, we do not transfer any field related to the missing labels;

  • +
  • For _all functions, we do transfer all the fields related to the missing labels.

  • +
+
+
+
+dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict)
+

Transfer the data fields specified in include_dict from a distributed zone +to the corresponding partitioned zones.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+dist_zone = PT.get_all_Zone_t(dist_tree)[0]
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+part_zones = PT.get_all_Zone_t(part_tree)
+
+include_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'],
+                'BCDataSet_t'    : ['*']}
+maia.transfer.dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict)
+
+for zone in part_zones:
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+  assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is     None
+  assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_zones_to_dist_zone_only(dist_zone, part_zones, comm, include_dict)
+

Transfer the data fields specified in include_dict from the partitioned zones +to the corresponding distributed zone.

+
+ +
+
+dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict={})
+

Transfer all the data fields, excepted those specified in exclude_dict, +from a distributed zone to the corresponding partitioned zones.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+dist_zone = PT.get_all_Zone_t(dist_tree)[0]
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+part_zones = PT.get_all_Zone_t(part_tree)
+
+exclude_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'],
+                'BCDataSet_t'    : ['*']}
+maia.transfer.dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict)
+
+for zone in part_zones:
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is     None
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataY') is not None
+  assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+  assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None
+
+
+
+ +
+
+part_zones_to_dist_zone_all(dist_zone, part_zones, comm, exclude_dict={})
+

Transfer all the data fields, excepted those specified in exclude_dict, +from the partitioned zone to the corresponding distributed zone.

+
+ +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.2/user_manual/user_manual.html b/docs/1.2/user_manual/user_manual.html new file mode 100644 index 00000000..8581c1dd --- /dev/null +++ b/docs/1.2/user_manual/user_manual.html @@ -0,0 +1,297 @@ + + + + + + + + + + User Manual — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

User Manual

+

Maia methods are accessible through three main modules :

+
    +
  • Factory allows to generate Maia trees, generally from another kind of tree +(e.g. the partitioning operation). Factory functions return a new tree +whose nature generally differs from the input tree.

  • +
  • Algo is the main Maia module and provides parallel algorithms to be applied on Maia trees. +Some algorithms are only available for distributed trees +and some are only available for partitioned trees. +A few algorithms are implemented for both kind +of trees and are thus directly accessible through the algo module.

    +

    Algo functions either modify their input tree inplace, or return some data, but they do not change the nature +of the tree.

    +
  • +
  • Transfer is a small module allowing to transfer data between Maia trees. A transfer function operates +on two existing trees and enriches the destination tree with data fields of the source tree.

  • +
+

Using Maia trees in your application often consists in chaining functions from these different modules.

+../_images/workflow.svg

A typical workflow could be:

+
    +
  1. Load a structured tree from a file, which produces a dist tree.

  2. +
  3. Apply some distributed algorithms to this tree: for example a structured to unstructured conversion (algo.dist module).

  4. +
  5. Generate a corresponding partitionned tree (factory module).

  6. +
  7. Apply some partitioned algorithms to the part tree, such as wall distance computation (algo.part module), +and even call you own tools (e.g. a CFD solver)

  8. +
  9. Transfer the resulting fields to the dist tree (transfer module).

  10. +
  11. Save the updated dist tree to disk.

  12. +
+

This user manuel describes the main functions available in each module.

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.2 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/.buildinfo b/docs/1.3/.buildinfo new file mode 100644 index 00000000..a008f5d4 --- /dev/null +++ b/docs/1.3/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: a092b494dc159b7cf6a4872bae0708c1 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/1.3/.doctrees/developer_manual/algo_description.doctree b/docs/1.3/.doctrees/developer_manual/algo_description.doctree new file mode 100644 index 00000000..ae355173 Binary files /dev/null and b/docs/1.3/.doctrees/developer_manual/algo_description.doctree differ diff --git a/docs/1.3/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree b/docs/1.3/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree new file mode 100644 index 00000000..323ceae5 Binary files /dev/null and b/docs/1.3/.doctrees/developer_manual/algo_description/elements_to_ngons.doctree differ diff --git a/docs/1.3/.doctrees/developer_manual/developer_manual.doctree b/docs/1.3/.doctrees/developer_manual/developer_manual.doctree new file mode 100644 index 00000000..83dfb290 Binary files /dev/null and b/docs/1.3/.doctrees/developer_manual/developer_manual.doctree differ diff --git a/docs/1.3/.doctrees/developer_manual/logging.doctree b/docs/1.3/.doctrees/developer_manual/logging.doctree new file mode 100644 index 00000000..cc3a7f36 Binary files /dev/null and b/docs/1.3/.doctrees/developer_manual/logging.doctree differ diff --git a/docs/1.3/.doctrees/developer_manual/maia_dev/conventions.doctree b/docs/1.3/.doctrees/developer_manual/maia_dev/conventions.doctree new file mode 100644 index 00000000..ce9c34f6 Binary files /dev/null and b/docs/1.3/.doctrees/developer_manual/maia_dev/conventions.doctree differ diff --git a/docs/1.3/.doctrees/developer_manual/maia_dev/development_workflow.doctree b/docs/1.3/.doctrees/developer_manual/maia_dev/development_workflow.doctree new file mode 100644 index 00000000..731aaaf2 Binary files /dev/null and b/docs/1.3/.doctrees/developer_manual/maia_dev/development_workflow.doctree differ diff --git a/docs/1.3/.doctrees/environment.pickle b/docs/1.3/.doctrees/environment.pickle new file mode 100644 index 00000000..94209e92 Binary files /dev/null and b/docs/1.3/.doctrees/environment.pickle differ diff --git a/docs/1.3/.doctrees/index.doctree b/docs/1.3/.doctrees/index.doctree new file mode 100644 index 00000000..720bfac2 Binary files /dev/null and b/docs/1.3/.doctrees/index.doctree differ diff --git a/docs/1.3/.doctrees/installation.doctree b/docs/1.3/.doctrees/installation.doctree new file mode 100644 index 00000000..704c8df3 Binary files /dev/null and b/docs/1.3/.doctrees/installation.doctree differ diff --git a/docs/1.3/.doctrees/introduction/introduction.doctree b/docs/1.3/.doctrees/introduction/introduction.doctree new file mode 100644 index 00000000..2505f629 Binary files /dev/null and b/docs/1.3/.doctrees/introduction/introduction.doctree differ diff --git a/docs/1.3/.doctrees/license.doctree b/docs/1.3/.doctrees/license.doctree new file mode 100644 index 00000000..1b44b160 Binary files /dev/null and b/docs/1.3/.doctrees/license.doctree differ diff --git a/docs/1.3/.doctrees/maia_pytree/basic.doctree b/docs/1.3/.doctrees/maia_pytree/basic.doctree new file mode 100644 index 00000000..459be24e Binary files /dev/null and b/docs/1.3/.doctrees/maia_pytree/basic.doctree differ diff --git a/docs/1.3/.doctrees/maia_pytree/inspect.doctree b/docs/1.3/.doctrees/maia_pytree/inspect.doctree new file mode 100644 index 00000000..e1bcf11e Binary files /dev/null and b/docs/1.3/.doctrees/maia_pytree/inspect.doctree differ diff --git a/docs/1.3/.doctrees/maia_pytree/presets.doctree b/docs/1.3/.doctrees/maia_pytree/presets.doctree new file mode 100644 index 00000000..4530d7b4 Binary files /dev/null and b/docs/1.3/.doctrees/maia_pytree/presets.doctree differ diff --git a/docs/1.3/.doctrees/maia_pytree/pytree_module.doctree b/docs/1.3/.doctrees/maia_pytree/pytree_module.doctree new file mode 100644 index 00000000..5fd56ada Binary files /dev/null and b/docs/1.3/.doctrees/maia_pytree/pytree_module.doctree differ diff --git a/docs/1.3/.doctrees/maia_pytree/search.doctree b/docs/1.3/.doctrees/maia_pytree/search.doctree new file mode 100644 index 00000000..e67ae45f Binary files /dev/null and b/docs/1.3/.doctrees/maia_pytree/search.doctree differ diff --git a/docs/1.3/.doctrees/quick_start.doctree b/docs/1.3/.doctrees/quick_start.doctree new file mode 100644 index 00000000..5349ae10 Binary files /dev/null and b/docs/1.3/.doctrees/quick_start.doctree differ diff --git a/docs/1.3/.doctrees/related_projects.doctree b/docs/1.3/.doctrees/related_projects.doctree new file mode 100644 index 00000000..164ab651 Binary files /dev/null and b/docs/1.3/.doctrees/related_projects.doctree differ diff --git a/docs/1.3/.doctrees/releases/release_notes.doctree b/docs/1.3/.doctrees/releases/release_notes.doctree new file mode 100644 index 00000000..71ea8d79 Binary files /dev/null and b/docs/1.3/.doctrees/releases/release_notes.doctree differ diff --git a/docs/1.3/.doctrees/user_manual/algo.doctree b/docs/1.3/.doctrees/user_manual/algo.doctree new file mode 100644 index 00000000..355ef75f Binary files /dev/null and b/docs/1.3/.doctrees/user_manual/algo.doctree differ diff --git a/docs/1.3/.doctrees/user_manual/config.doctree b/docs/1.3/.doctrees/user_manual/config.doctree new file mode 100644 index 00000000..0dda8b2d Binary files /dev/null and b/docs/1.3/.doctrees/user_manual/config.doctree differ diff --git a/docs/1.3/.doctrees/user_manual/factory.doctree b/docs/1.3/.doctrees/user_manual/factory.doctree new file mode 100644 index 00000000..ec40b6c0 Binary files /dev/null and b/docs/1.3/.doctrees/user_manual/factory.doctree differ diff --git a/docs/1.3/.doctrees/user_manual/io.doctree b/docs/1.3/.doctrees/user_manual/io.doctree new file mode 100644 index 00000000..9c635a7b Binary files /dev/null and b/docs/1.3/.doctrees/user_manual/io.doctree differ diff --git a/docs/1.3/.doctrees/user_manual/transfer.doctree b/docs/1.3/.doctrees/user_manual/transfer.doctree new file mode 100644 index 00000000..096345f7 Binary files /dev/null and b/docs/1.3/.doctrees/user_manual/transfer.doctree differ diff --git a/docs/1.3/.doctrees/user_manual/user_manual.doctree b/docs/1.3/.doctrees/user_manual/user_manual.doctree new file mode 100644 index 00000000..ce837137 Binary files /dev/null and b/docs/1.3/.doctrees/user_manual/user_manual.doctree differ diff --git a/docs/1.3/.nojekyll b/docs/1.3/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/1.3/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns b/docs/1.3/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns new file mode 100644 index 00000000..c7b15944 Binary files /dev/null and b/docs/1.3/_downloads/1ed8318e8901b7e753ee4b97343fad2d/S_twoblocks.cgns differ diff --git a/docs/1.3/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns b/docs/1.3/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns new file mode 100644 index 00000000..5fc9da57 Binary files /dev/null and b/docs/1.3/_downloads/26be40da44a755a9b14f190564484749/U_ATB_45.cgns differ diff --git a/doc/maia_pytree/images/badges.svg b/docs/1.3/_images/badges.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/maia_pytree/images/badges.svg rename to docs/1.3/_images/badges.svg diff --git a/docs/1.3/_images/data_dist.svg b/docs/1.3/_images/data_dist.svg new file mode 100644 index 00000000..5161b893 --- /dev/null +++ b/docs/1.3/_images/data_dist.svg @@ -0,0 +1,265 @@ + + + +(a)(b) diff --git a/docs/1.3/_images/data_dist_gnum.svg b/docs/1.3/_images/data_dist_gnum.svg new file mode 100644 index 00000000..0270c247 --- /dev/null +++ b/docs/1.3/_images/data_dist_gnum.svg @@ -0,0 +1,229 @@ + + + +dist=[0,3,5,7] diff --git a/docs/1.3/_images/data_full.svg b/docs/1.3/_images/data_full.svg new file mode 100644 index 00000000..773e4861 --- /dev/null +++ b/docs/1.3/_images/data_full.svg @@ -0,0 +1,161 @@ + + + +1234567 diff --git a/docs/1.3/_images/data_part.svg b/docs/1.3/_images/data_part.svg new file mode 100644 index 00000000..702ee2cc --- /dev/null +++ b/docs/1.3/_images/data_part.svg @@ -0,0 +1,307 @@ + + + +(a)(b)(c) diff --git a/docs/1.3/_images/data_part_gnum.svg b/docs/1.3/_images/data_part_gnum.svg new file mode 100644 index 00000000..5f8bad52 --- /dev/null +++ b/docs/1.3/_images/data_part_gnum.svg @@ -0,0 +1,252 @@ + + + +LN_to_GN=[1,6,4,3]LN_to_GN=[2,7,5]LN_to_GN=[3,5] diff --git a/docs/1.3/_images/dist_mesh.svg b/docs/1.3/_images/dist_mesh.svg new file mode 100644 index 00000000..cab59cbc --- /dev/null +++ b/docs/1.3/_images/dist_mesh.svg @@ -0,0 +1,491 @@ + + + +image/svg+xml1 +4 +3 +2 +6 +5 +1 +9 +8 +7 +2 +10 +11 +6 +3 +12 +4 +5 + \ No newline at end of file diff --git a/docs/1.3/_images/dist_mesh_arrays.svg b/docs/1.3/_images/dist_mesh_arrays.svg new file mode 100644 index 00000000..99abc251 --- /dev/null +++ b/docs/1.3/_images/dist_mesh_arrays.svg @@ -0,0 +1,1073 @@ + + + +image/svg+xmlCoordinateX +CoordinateY +Connectivity +1 +2 +3 +4 +5 +6 +1 2 3 4 5 6 7 8 9 +10 +11 +12 +1 9 +10 +2 +2 +10 +12 +3 8 7 6 +11 +9 8 +111011 +6 5 4 +10 +11 +4 +12 + diff --git a/docs/1.3/_images/dist_part_LN_to_GN.svg b/docs/1.3/_images/dist_part_LN_to_GN.svg new file mode 100644 index 00000000..91c01db2 --- /dev/null +++ b/docs/1.3/_images/dist_part_LN_to_GN.svg @@ -0,0 +1,2169 @@ + + + +image/svg+xml1 +4 +3 +2 +6 +5 +1 +9 +8 +7 +2 +10 +11 +6 +3 +12 +4 +5 +8 +7 +6 +5 +4 +1 +2 +3 +6 +7 +8 +3 +4 +5 +1 +2 +2 +1 +3 +1 +2 +3 +LN to GN of +vertices +LN +to +GN of +elements +1 2 3 4 5 6 7 8 +1 2 3 4 5 6 7 8 +2 1 6 +3 +12 +4 +11 +10 +2 9 1 +4 5 +10 +11 +6 9 8 7 +4 3 5 +1 +2 +3 +1 +2 +3 +Distributed +mesh +Sub +- +mesh +0 +Sub +- +mesh +1 +Sub +- +mesh +0 +Sub +- +mesh +1 + \ No newline at end of file diff --git a/docs/1.3/_images/dist_tree.png b/docs/1.3/_images/dist_tree.png new file mode 100644 index 00000000..d62c4cb4 Binary files /dev/null and b/docs/1.3/_images/dist_tree.png differ diff --git a/docs/1.3/_images/dist_tree_expl.png b/docs/1.3/_images/dist_tree_expl.png new file mode 100644 index 00000000..ae830142 Binary files /dev/null and b/docs/1.3/_images/dist_tree_expl.png differ diff --git a/docs/1.3/_images/full_mesh.svg b/docs/1.3/_images/full_mesh.svg new file mode 100644 index 00000000..6dc266fe --- /dev/null +++ b/docs/1.3/_images/full_mesh.svg @@ -0,0 +1,509 @@ + + + +image/svg+xml1 + +4 + +3 + +2 + +6 + +5 + +1 + +9 + +8 + +7 + +2 + +10 + +11 + +6 + +3 + +12 + +4 + +5 + + \ No newline at end of file diff --git a/doc/_static/logo_maia.svg b/docs/1.3/_images/logo_maia.svg old mode 100755 new mode 100644 similarity index 100% rename from doc/_static/logo_maia.svg rename to docs/1.3/_images/logo_maia.svg diff --git a/docs/1.3/_images/part_mesh.svg b/docs/1.3/_images/part_mesh.svg new file mode 100644 index 00000000..a008bd94 --- /dev/null +++ b/docs/1.3/_images/part_mesh.svg @@ -0,0 +1,598 @@ + + + +image/svg+xml8 +7 +6 +5 +4 +1 +2 +3 +6 +7 +8 +3 +4 +5 +1 +2 +2 +1 +3 +1 +2 +3 + \ No newline at end of file diff --git a/docs/1.3/_images/part_mesh_arrays.svg b/docs/1.3/_images/part_mesh_arrays.svg new file mode 100644 index 00000000..110edd78 --- /dev/null +++ b/docs/1.3/_images/part_mesh_arrays.svg @@ -0,0 +1,888 @@ + + + +image/svg+xmlCoordinateX +CoordinateY +Connectivity +1 +2 +3 +1 +2 +3 +1 2 3 4 5 6 7 8 +1 2 3 4 5 6 7 8 +6 5 2 1 8 7 5 6 +5 4 3 2 +6 7 4 3 7 8 5 4 +4 5 2 1 + diff --git a/docs/1.3/_images/part_tree.png b/docs/1.3/_images/part_tree.png new file mode 100644 index 00000000..eced07ef Binary files /dev/null and b/docs/1.3/_images/part_tree.png differ diff --git a/docs/1.3/_images/part_tree_expl.png b/docs/1.3/_images/part_tree_expl.png new file mode 100644 index 00000000..843ae4e3 Binary files /dev/null and b/docs/1.3/_images/part_tree_expl.png differ diff --git a/doc/maia_pytree/images/pip.png b/docs/1.3/_images/pip.png old mode 100755 new mode 100644 similarity index 100% rename from doc/maia_pytree/images/pip.png rename to docs/1.3/_images/pip.png diff --git a/docs/1.3/_images/qs_basic.png b/docs/1.3/_images/qs_basic.png new file mode 100644 index 00000000..b17d8907 Binary files /dev/null and b/docs/1.3/_images/qs_basic.png differ diff --git a/docs/1.3/_images/qs_pycgns.png b/docs/1.3/_images/qs_pycgns.png new file mode 100644 index 00000000..54176820 Binary files /dev/null and b/docs/1.3/_images/qs_pycgns.png differ diff --git a/docs/1.3/_images/qs_workflow.png b/docs/1.3/_images/qs_workflow.png new file mode 100644 index 00000000..e4b26743 Binary files /dev/null and b/docs/1.3/_images/qs_workflow.png differ diff --git a/doc/maia_pytree/images/sids_warning.png b/docs/1.3/_images/sids_warning.png old mode 100755 new mode 100644 similarity index 100% rename from doc/maia_pytree/images/sids_warning.png rename to docs/1.3/_images/sids_warning.png diff --git a/docs/1.3/_images/tree_seq.png b/docs/1.3/_images/tree_seq.png new file mode 100644 index 00000000..9fcff12c Binary files /dev/null and b/docs/1.3/_images/tree_seq.png differ diff --git a/doc/maia_pytree/images/vscode_hints.png b/docs/1.3/_images/vscode_hints.png similarity index 100% rename from doc/maia_pytree/images/vscode_hints.png rename to docs/1.3/_images/vscode_hints.png diff --git a/docs/1.3/_images/workflow.svg b/docs/1.3/_images/workflow.svg new file mode 100644 index 00000000..dd21687a --- /dev/null +++ b/docs/1.3/_images/workflow.svg @@ -0,0 +1,669 @@ + + + +image/svg+xmldist_tree +Field +transfers +Distributed +algorithms +zone +merges +, +connectivity +transformations… +Partitioned +algorithms +Solver +, +wall +distances, +HPC +renumbering + +Part_tree +Part_tree +part_tree +Creation +by +partitioning +CGNS +File + \ No newline at end of file diff --git a/docs/1.3/_sources/developer_manual/algo_description.rst.txt b/docs/1.3/_sources/developer_manual/algo_description.rst.txt new file mode 100644 index 00000000..1e5eb7a7 --- /dev/null +++ b/docs/1.3/_sources/developer_manual/algo_description.rst.txt @@ -0,0 +1,10 @@ +Algorithms description +====================== + +This section provides a detailed description of some algorithms. + +.. toctree:: + :maxdepth: 1 + + algo_description/elements_to_ngons.rst + diff --git a/docs/1.3/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt b/docs/1.3/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt new file mode 100644 index 00000000..4827afef --- /dev/null +++ b/docs/1.3/_sources/developer_manual/algo_description/elements_to_ngons.rst.txt @@ -0,0 +1,230 @@ +.. _elements_to_ngons_impl: + +elements_to_ngons +================= + +Description +----------- + +.. code-block:: python + + maia.algo.dist.elements_to_ngons(dist_tree_elts, comm) + +Take a **distributed** :cgns:`CGNSTree_t` or :cgns:`CGNSBase_t`, and transform it into a **distributed** :cgns:`NGon/NFace` mesh. The tree is modified in-place. + +Example +------- + +.. literalinclude:: ../../user_manual/snippets/test_algo.py + :start-after: #elements_to_ngons@start + :end-before: #elements_to_ngons@end + :dedent: 2 + +Arguments +--------- + +:code:`dist_tree_elts` + a :ref:`distributed tree ` with + * unstructured zones that contain regular element sections (i.e. not NGON, not NFACE and not MIXED) + * the :ref:`Maia tree ` property + +:code:`comm` + a communicator over which :code:`elements_to_ngons` is called + +Output +------ + +The tree is modified in-place. The regular element sections are replaced by: + +* a NGon section with: + + * An :cgns:`ElementStartOffset`/:cgns:`ElementConnectivity` node describing: + + * first the external faces in exactly the same order as they were (in particular, gathered by face type) + * then the internal faces, also gathered by face type + + * a :cgns:`ParentElements` node and a :cgns:`ParentElementsPosition` node + +* a NFace section with cells ordered as they were, with ids only shifted by the number of interior faces + + +Parallelism +----------- + +:code:`elements_to_ngons` is a collective function. + +Complexity +---------- + +With :math:`N` the number of zones, :math:`n_i` the number of elements of zone :math:`i`, :math:`n_{f,i}` its number of interior faces, and :math:`K` the number of processes + +Sequential time + :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i \cdot log(n_i) \right)` + + The cost is dominated by the sorting of faces (interior and exterior) used to spot identical ones. + +Parallel time + :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i/K \cdot log(n_i) \right)` + + The parallel distributed sort algorithm consists of three steps: + + 1. A partitioning step that locally gather faces into :math:`K` buckets that are of equal global size. This uses a :math:`K`-generalized variant of `quickselect `_ that is of complexity :math:`\displaystyle \mathcal{O} \left( n_i/K \cdot log(K) \right)` + 2. An all_to_all exchange step that gather the :math:`K` buckets on the :math:`K` processes. This step is not accounted for here (see below) + 3. A local sorting step that is :math:`\displaystyle \mathcal{O} \left( n_i/K \cdot log(n_i/K) \right)` + + If we sum up step 1 and 3, we get + +.. math:: + + \begin{equation} \label{eq1} + \begin{split} + \textrm{C}_\textrm{parallel_sort} & = \mathcal{O} \left( n_i/K \cdot log(K) + n_i/K \cdot log(n_i/K) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot \left( log(K) + log(n_i/K) \right) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot log(n_i) \right) + \end{split} + \end{equation} + +Theoretical scaling + :math:`\textrm{Speedup} = K` + + Experimentally, the scaling is much worse - under investigation. + + Note: the speedup is computed by :math:`\textrm{Speedup} = t_s / t_p` where :math:`t_s` is the sequential time and :math:`t_p` the parallel time. A speedup of :math:`K` is perfect, a speedup lower than :math:`1` means that sequential execution is faster. + +Peak memory + Approximately :math:`\displaystyle \sum_{i=0}^N 2 n_i + n_{f,i}` + + This is the size of the input tree + the output tree. :math:`n_i` is counted twice: once for the input element connectivity, once for the output NFace connectivity + +Size of communications + Approximately :math:`\displaystyle \sum_{i=0}^N 3 n_{f,i} + n_i` **all_to_all** calls + + For each zone, one **all_to_all** call to sort interior faces, one to send back the faces to the NFace, one to concatenate all faces, one to concatenate all cells + +Number of communication calls + Should be :math:`\displaystyle \mathcal{O} \left( \sum_{i=0}^N log(n_i/K) \right)` + + The number of communications is constant, except for the algorithm finding a balanced distribution of interior faces + +Note + In practice, :math:`n_{f,i}` varies from :math:`2 n_i` (tet-dominant meshes) to :math:`3 n_i` (hex-dominant meshes). + +Algorithm explanation +--------------------- + +.. code-block:: c++ + + maia::elements_to_ngons(cgns::tree& dist_zone, MPI_Comm comm) + +The algorithm is divided in two steps: + +1. Generate interior faces, insert them between exterior faces and cells. During the process, also compute the parent information (:cgns:`ParentElements` and :cgns:`ParentElementsPosition`) and add the :cgns:`CellFace` connectivity to cells. All elements keep their standard element types, in particular interior faces are inserted by section type (e.g. there will be a :cgns:`TRI_3_interior` node and a :cgns:`QUAD_4_interior` node, not a :cgns:`NGon` node) +2. Transform all sections into :cgns:`NGon/NFace` + +Generate interior faces +^^^^^^^^^^^^^^^^^^^^^^^ + +Simplified algorithm +"""""""""""""""""""" + +Let us look at a simplified sequential algorithm first: + +.. code-block:: text + + 1. For all element sections (3D and 2D): + Generate faces + => FaceVtx arrays (one for Tris, one for Quads) + => Associated Parent and ParentPosition arrays + (only one parent by element at this stage) + Interior faces are generated twice (by their two parent cells) + Exterior faces are generated twice (by a parent cell and a boundary face) + + 2. For each element kind in {Tri,Quad} + Sort the FaceVtx array + (ordering: lexicographical comparison of vertices) + Sort the parent arrays accordingly + => now each face appears consecutively exactly twice + for interior faces, + the FaceVtx is always inverted between the two occurences + for exterior faces, + it depends on the normal convention + + Note that interior and exterior faces can be distinguised + by looking at the id of their parents + + 3. For each interior face appearing twice: + Create an interior face section where: + the first FaceVtx is kept + the two parents are stored + 4. For each exterior face appearing twice: + One of the face is the original boundary section face, + one was generated from the joint cell + Send back to the original boundary face its parent face id and position + => store the parent information of the boundary face + +Parallel algorithm +"""""""""""""""""" + +The algorithm is very similar to the sequential one. We need to modify two operations: + +Sorting of the FaceVtx array (step 2) + The parallel sorting is done in three steps: + + 1. apply a partial sort :cpp:`std_e::sort_by_rank` that will determine the rank of each FaceVtx + 2. call an :cpp:`all_to_all` communication step that sends each connectivity to its rank, based on the information of the previous step + 3. sort each received FaceVtx locally + +Send back boundary parents and position to the original boundary faces (step 4) + Since the original face can be remote, this is a parallel communication operation using :cpp:`std_e::scatter` + +Computation of the CellFace +""""""""""""""""""""""""""" + +After step 2, we have all the faces exactly once, with their parent ids and parent positions. +We can then compute the CellFace of each cell section by the following algorithm: + +.. code-block:: text + + For each cell section: + pre-allocate the CellFace array + (its size is n_cell_in_section * n_face_of_cell_type) + view it as a global distributed array + For each unique face: + For each of its parent cells (could be one or two): + send the parent cell the id of the face and its position + insert the result in the CellFace array + +As previously, the send operation uses a **scatter** pattern + +Transform all sections into NGon/NFace +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Thanks to the previous algorithm, we have: + +* all exterior and interior faces with their parent information +* the CellFace connectivity of the cell sections + +Elements are ordered in something akin to this: + +* boundary tris +* boundary quads +* internal tris +* internal quads + +* tetras +* pyras +* prisms +* hexas + +The algorithm then just needs to: + +* concatenate all FaceVtx of the faces into a :cgns:`NGon` node and add a :cgns:`ElementStartOffset` +* concatenate all CellFace of the cells into a :cgns:`NFace` node and add a :cgns:`ElementStartOffset` + +Design alternatives +------------------- + +The final step of the computation involves concatenating faces and cells global section by global section. This requires two heavyweight **all_to_all** calls. An alternative would be to concatenate locally. This would imply two trade-offs: + +* the faces and cells would then not be globally gathered by type, and the exterior faces would not be first +* all the :cgns:`PointLists` (including those where :cgns:`GridLocation=FaceCenter`) would have to be shifted diff --git a/docs/1.3/_sources/developer_manual/developer_manual.rst.txt b/docs/1.3/_sources/developer_manual/developer_manual.rst.txt new file mode 100644 index 00000000..ab887981 --- /dev/null +++ b/docs/1.3/_sources/developer_manual/developer_manual.rst.txt @@ -0,0 +1,13 @@ +.. _dev_manual: + +################ +Developer Manual +################ + +.. toctree:: + :maxdepth: 1 + + logging + maia_dev/conventions + maia_dev/development_workflow + algo_description diff --git a/docs/1.3/_sources/developer_manual/logging.rst.txt b/docs/1.3/_sources/developer_manual/logging.rst.txt new file mode 100644 index 00000000..9592d08f --- /dev/null +++ b/docs/1.3/_sources/developer_manual/logging.rst.txt @@ -0,0 +1,122 @@ +.. _logging: + +Log management +============== + + +Loggers +------- + +A **logger** is a global object where an application or a library can log to. +It can be declared with + +.. literalinclude:: snippets/test_logging.py + :start-after: #add_logger@start + :end-before: #add_logger@end + :dedent: 2 + +or with the equivalent C++ code + +.. code-block:: C++ + + #include "std_e/logging/log.hpp" + + std_e::add_logger("my_logger"); + +A logger declared in Python is available in C++ and vice-versa: we do not need to create it twice. + +.. note:: Loggers are available to pure C/C++ programs (in particular, test programs that do not use the Python interpreter). + +It can then be referred to by its name. If we want to log a string to ``my_logger``, we will do it like so: + +.. literalinclude:: snippets/test_logging.py + :start-after: #log@start + :end-before: #log@end + :dedent: 2 + +.. code-block:: C++ + + #include "std_e/logging/log.hpp" + + std_e::log("my_logger", "my message"); + +Typically, an application will use several loggers to log messages by themes. By convention, the name of a logger in the application ``my_app`` should begin with ``my_app``. For instance, loggers can be named: ``my_app``, ``my_app-stats``, ``my_app-errors``... + +Loggers are both + +- developper-oriented, that is, when a developper wants to output a message, he should select a logger (existing or not) encompassing the theme of the message he wants to log, +- user-oriented, because a user can choose what logger he wants to listen to. + + + +Printers +-------- + +By itself, a logger does not do anything with the messages it receives. For that, we need to attach **printers** to a logger that will handle its messages. + +For instance, we can attach a printer that will output the message to the console: + +.. literalinclude:: snippets/test_logging.py + :start-after: #add_printer@start + :end-before: #add_printer@end + :dedent: 2 + +Printers are user-oriented: it is the user of the application who decides what he wants to do with the message that each logger is receiving, by attaching none or several printers to a logger. + +Available printers +^^^^^^^^^^^^^^^^^^ + +stdout_printer, stderr_printer + output messages to the console (respectively stdout and stderr) + +mpi_stdout_printer, mpi_stderr_printer + output messages to the console, but prefix them by ``MPI.COMM_WORLD.Get_rank()`` + +mpi_rank_0_stdout_printer, mpi_rank_0_stderr_printer + output messages to the console, if ``MPI.COMM_WORLD.Get_rank()==0`` + +file_printer('my_file.extension') + output messages to file ``my_file.extension`` + +mpi_file_printer('my_file.extension') + output messages to files ``my_file.{rk}.extension``, with ``rk = MPI.COMM_WORLD.Get_rank()`` + +.. note:: + MPI-aware printers use ``MPI.COMM_WORLD`` rather than a local communicator. While the latter would be more correct, it would imply local instanciation of printers, at the time or after a new communicator is created. While this is certainly doable, up until now we feel that it does not worth the burden. But if the need arises, they can still be added. + +Create your own printer +^^^^^^^^^^^^^^^^^^^^^^^ + +Any Python type can be used as a printer as long as it provides a ``log`` method that accepts a string argument. + +.. literalinclude:: snippets/test_logging.py + :start-after: #create_printer@start + :end-before: #create_printer@end + :dedent: 2 + + +Configuration file +------------------ + +Loggers are associated to default printers. While they can be configured anytime in the Python scripts, most of the time, reading a configuration file at the start of the program is enough. The program will try to read a configuration file if the environment variable ``LOGGING_CONF_FILE`` is set. A logging configuration file looks like this: + + +.. code-block:: Text + + my_app : mpi_stdout_printer + my_app-my_theme : mpi_file_printer('my_theme.log') + +For developpers, a logging file ``logging.conf`` with loggers and default printers is put in the ``build/`` folder, and ``LOGGING_CONF_FILE`` is set accordingly. + +Maia specifics +-------------- + +Maia provides 4 convenience functions that use Maia loggers + +.. code-block:: python + + from maia.utils import logging as mlog + mlog.info('info msg') # uses the 'maia' logger + mlog.stat('stat msg') # uses the 'maia-stats' logger + mlog.warning('warn msg') # uses the 'maia-warnings' logger + mlog.error('error msg') # uses the 'maia-errors' logger diff --git a/docs/1.3/_sources/developer_manual/maia_dev/conventions.rst.txt b/docs/1.3/_sources/developer_manual/maia_dev/conventions.rst.txt new file mode 100644 index 00000000..f171f99a --- /dev/null +++ b/docs/1.3/_sources/developer_manual/maia_dev/conventions.rst.txt @@ -0,0 +1,29 @@ +Conventions +=========== + +Naming conventions +------------------ + +* **snake_case** +* A variable holding a number of things is written :code:`n_thing`. Example: :code:`n_proc`, :code:`n_vtx`. The suffix is singular. +* For unit tests, when testing variable :code:``, the hard-coded expected variable is named :code:`expected_`. +* Usual abbreviations + + * **elt** for **element** + * **vtx** for **vertex** + * **proc** for **process** + * **sz** for **size** (only for local variable names, not functions) + +* Connectivities + + * **cell_vtx** means mesh array of cells described by their vertices (CGNS example: :cgns:`HEXA_8`) + * **cell_face** means the cells described by their faces (CGNS example: :cgns:`NFACE_n`) + * **face_cell** means for each face, the two parent cells (CGNS example: :cgns:`ParentElement`) + * ... so on: **face_vtx**, **edge_vtx**... + * **elt_vtx**, **elt_face**...: in this case, **elt** can be either a cell, a face or an edge + + +Other conventions +----------------- + +We try to use semi-open intervals and 0-indexed structures. This is coherent with Python, C++, and their libraries, but unfortunately not with CGNS. diff --git a/docs/1.3/_sources/developer_manual/maia_dev/development_workflow.rst.txt b/docs/1.3/_sources/developer_manual/maia_dev/development_workflow.rst.txt new file mode 100644 index 00000000..96231c45 --- /dev/null +++ b/docs/1.3/_sources/developer_manual/maia_dev/development_workflow.rst.txt @@ -0,0 +1,34 @@ +Development workflow +==================== + +Sub-modules +----------- + +The **Maia** repository is compatible with the development process described `here `_. It uses git submodules to ease the joint development with other repositories compatible with this organization. + +TL;DR: configure the git repository by sourcing `this file `_ and then execute: + +.. code-block:: bash + + cd $MAIA_FOLDER + git submodule update --init + git_config_submodules + + +Launch tests +------------ + +Tests can be launched by calling CTest, but during the development, we often want to parameterize how to run tests (which ones, number of processes, verbosity level...). + +There is a :code:`source.sh` generated in the :code:`build/` folder. It can be sourced in order to get the correct environment to launch the tests (notably, it updates :code:`LD_LIBRARY_PATH` and :code:`PYTHONPATH` to point to build artifacts). + +Tests can be called with e.g.: + +.. code:: bash + + cd $PROJECT_BUILD_DIR + source source.sh + mpirun -np 4 external/std_e/std_e_unit_tests + ./external/cpp_cgns/cpp_cgns_unit_tests + mpirun -np 4 test/maia_doctest_unit_tests + mpirun -np 4 pytest $PROJECT_SRC_DIR/maia --with-mpi diff --git a/doc/index.rst b/docs/1.3/_sources/index.rst.txt similarity index 100% rename from doc/index.rst rename to docs/1.3/_sources/index.rst.txt diff --git a/docs/1.3/_sources/installation.rst.txt b/docs/1.3/_sources/installation.rst.txt new file mode 100644 index 00000000..d0a8ffcc --- /dev/null +++ b/docs/1.3/_sources/installation.rst.txt @@ -0,0 +1,173 @@ +.. _installation: + +Installation +############ + +Prefered installation procedure +=============================== + +Maia depends on quite a few libraries of different kinds, be it system libraries like MPI, third-party libraries like HDF5, ONERA libraries like ParaDiGM and Cassiopée, git submodules (std_e...), or Python modules (mpi4py, ruamel). The prefered way of installing Maia in a coherent environment is by using the `Spack package manager `_. A Spack recipe for Maia can be found on the `ONERA Spack repository `_. + +Installation through Spack +-------------------------- + +1. Source a Spack repository on your machine. +2. If you don't have a Spack repository ready, you can download one with :code:`git clone https://github.com/spack/spack.git`. On ONERA machines, it is advised to use the `Spacky `_ helper. +3. Download the **ONERA Spack repository** with :code:`git clone https://gitlab.onera.net/informatics/infra/onera_spack_repo.git` +4. Tell Spack that package recipes are in :code:`onera_spack_repo` by adding the following lines to :code:`$SPACK_ROOT/etc/repos.yaml`: + +.. code-block:: yaml + + repos: + - path/to/onera_spack_repo + +(note that **spacky** does steps 3. and 4. for you) + +5. You should be able to see the package options of Maia with :code:`spack info maia` +6. To install Maia: :code:`spack install maia` + + +Development workflow +-------------------- + +For development, it is advised to use Spack to have Maia dependencies, but then follow a typical CMake workflow with :code:`cmake/make`. + + +Dependencies +^^^^^^^^^^^^ + +To get access to Maia dependencies in your development environment, you can: + +* Install a Spack version of Maia, source it in your development environment to get all the dependencies, then override with your own compiled version of Maia +* Do the same, but use a Spack environment containing Maia instead of just the Maia package +* Source a Spack environment where Maia has been removed from the environment view. This can be done by adding the following lines to the :code:`spack.yaml` environement file: + +.. code-block:: yaml + + view: + default: + exclude: ['maia'] + +This last option is cleaner because you are sure that you are not using another version of Maia (but it means you need to create or have access to such an environment view) + +Source the build folder +^^^^^^^^^^^^^^^^^^^^^^^ + +You can develop without the need to install Maia. However, in addition to sourcing your dependencies in your development environment, you also need to source the build artifacts by: + +.. code-block:: bash + + cd $MAIA_BUILD_FOLDER + source source.sh + +The :code:`source.sh` file is created by CMake and will source all Maia artifacts (dynamic libraries, python modules...) + + +Development workflow with submodules +------------------------------------ + +It is often practical to develop Maia with some of its dependencies, namely: + +* project_utils +* std_e +* cpp_cgns +* paradigm +* pytest_parallel + +For that, you need to use git submodules. Maia submodules are located at :code:`$MAIA_FOLDER/external`. To populate them, use :code:`git submodule update --init`. Once done, CMake will use these versions of the dependencies. If you don't populate the submodules, CMake will try to use the ones of your environment (for instance, the one installed by Spack). + +We advise that you use some additional submodule configuration utilities provided in `this file `_. In particular, you should use: + +.. code-block:: bash + + cd $MAIA_FOLDER + git submodule update --init + git_config_submodules + +The detailed meaning of `git_config_submodules` and the git submodule developper workflow of Maia is presented `here `_. + +If you are using Maia submodules, you can filter them out from your Spack environment view like so: + +.. code-block:: yaml + + view: + default: + exclude: ['maia','std-e','cpp-cgns','paradigm','pytest_parallel'] + +Manual installation procedure +============================= + +Dependencies +------------ + +**Maia** depends on: + +* python3 +* MPI +* hdf5 + +* Cassiopée + +* pytest >6 (python package) +* ruamel (python package) +* mpi4py (python package) + +The build process requires: + +* Cmake >= 3.14 +* GCC >= 8 (Clang and Intel should work but no CI) + + +Other dependencies +^^^^^^^^^^^^^^^^^^ + +During the build process, several other libraries will be downloaded: + +* pybind11 +* range-v3 +* doctest + +* ParaDiGM +* project_utils +* std_e +* cpp_cgns + +This process should be transparent. + + +Optional dependencies +^^^^^^^^^^^^^^^^^^^^^ + +The documentation build requires: + +* Doxygen >= 1.8.19 +* Breathe >= 4.15 (python package) +* Sphinx >= 3.00 (python package) + +Build and install +----------------- + +1. Install the required dependencies. They must be in your environment (:code:`PATH`, :code:`LD_LIBRARY_PATH`, :code:`PYTHONPATH`). + + For pytest, you may need these lines : + +.. code:: bash + + pip3 install --user pytest + pip3 install --user pytest-mpi + pip3 install --user pytest-html + pip3 install --user pytest_check + pip3 install --user ruamel.yaml + +2. Then you need to populate your :code:`external` folder. You can do it with :code:`git submodule update --init` + +3. Then use CMake to build maia, e.g. + +.. code:: bash + + SRC_DIR= + BUILD_DIR= + INSTALL_DIR= + cmake -S $SRC_DIR -B$BUILD_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR + cd $BUILD_DIR && make -j && make install + diff --git a/doc/introduction/introduction.rst b/docs/1.3/_sources/introduction/introduction.rst.txt similarity index 100% rename from doc/introduction/introduction.rst rename to docs/1.3/_sources/introduction/introduction.rst.txt diff --git a/docs/1.3/_sources/license.rst.txt b/docs/1.3/_sources/license.rst.txt new file mode 100644 index 00000000..24330d26 --- /dev/null +++ b/docs/1.3/_sources/license.rst.txt @@ -0,0 +1,390 @@ +.. _license: + +License +======= + +Mozilla Public License Version 2.0 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Definitions +^^^^^^^^^^^^^^ + +**1.1. "Contributor"** + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +**1.2. "Contributor Version"** + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +**1.3. "Contribution"** + means Covered Software of a particular Contributor. + +**1.4. "Covered Software"** + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +**1.5. "Incompatible With Secondary Licenses"** + means + +* **(a)** that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or +* **(b)** that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +**1.6. "Executable Form"** + means any form of the work other than Source Code Form. + +**1.7. "Larger Work"** + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +**1.8. "License"** + means this document. + +**1.9. "Licensable"** + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +**1.10. "Modifications"** + means any of the following: + +* **(a)** any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or +* **(b)** any new file in Source Code Form that contains any Covered + Software. + +**1.11. "Patent Claims" of a Contributor** + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +**1.12. "Secondary License"** + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +**1.13. "Source Code Form"** + means the form of the work preferred for making modifications. + +**1.14. "You" (or "Your")** + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means **(a)** the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or **(b)** ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + + +2. License Grants and Conditions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +2.1. Grants +~~~~~~~~~~~ + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +* **(a)** under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and +* **(b)** under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date +~~~~~~~~~~~~~~~~~~~ + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +* **(a)** for any code that a Contributor has removed from Covered Software; + or +* **(b)** for infringements caused by: **(i)** Your and any other third party's + modifications of Covered Software, or **(ii)** the combination of its + Contributions with other software (except as part of its Contributor + Version); or +* **(c)** under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses +~~~~~~~~~~~~~~~~~~~~~~~~ + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation +~~~~~~~~~~~~~~~~~~~ + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use +~~~~~~~~~~~~~ + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions +~~~~~~~~~~~~~~~ + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + + +3. Responsibilities +^^^^^^^^^^^^^^^^^^^^^^^ + +3.1. Distribution of Source Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If You distribute Covered Software in Executable Form then: + +* **(a)** such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +* **(b)** You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices +~~~~~~~~~~~~ + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + + +4. Inability to Comply Due to Statute or Regulation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: **(a)** comply with +the terms of this License to the maximum extent possible; and **(b)** +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + + +5. Termination +^^^^^^^^^^^^^^ + +**5.1.** The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated **(a)** provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and **(b)** on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +**5.2.** If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + + +6. Disclaimer of Warranty +^^^^^^^^^^^^^^^^^^^^^^^^^ + + Covered Software is provided under this License on an "as is" + basis, without warranty of any kind, either expressed, implied, or + statutory, including, without limitation, warranties that the + Covered Software is free of defects, merchantable, fit for a + particular purpose or non-infringing. The entire risk as to the + quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You + (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an + essential part of this License. No use of any Covered Software is + authorized under this License except under this disclaimer. + +7. Limitation of Liability +^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Under no circumstances and under no legal theory, whether tort + (including negligence), contract, or otherwise, shall any + Contributor, or anyone who distributes Covered Software as + permitted above, be liable to You for any direct, indirect, + special, incidental, or consequential damages of any character + including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any + and all other commercial damages or losses, even if such party + shall have been informed of the possibility of such damages. This + limitation of liability shall not apply to liability for death or + personal injury resulting from such party's negligence to the + extent applicable law prohibits such limitation. Some + jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and + limitation may not apply to You. + + +8. Litigation +^^^^^^^^^^^^^ + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + + +9. Miscellaneous +^^^^^^^^^^^^^^^^ + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + + +10. Versions of the License +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +10.1. New Versions +~~~~~~~~~~~~~~~~~~ + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions +~~~~~~~~~~~~~~~~~~~~~~~ + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary License" Notice +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + + diff --git a/doc/maia_pytree/basic.rst b/docs/1.3/_sources/maia_pytree/basic.rst.txt similarity index 100% rename from doc/maia_pytree/basic.rst rename to docs/1.3/_sources/maia_pytree/basic.rst.txt diff --git a/doc/maia_pytree/inspect.rst b/docs/1.3/_sources/maia_pytree/inspect.rst.txt similarity index 100% rename from doc/maia_pytree/inspect.rst rename to docs/1.3/_sources/maia_pytree/inspect.rst.txt diff --git a/doc/maia_pytree/presets.rst b/docs/1.3/_sources/maia_pytree/presets.rst.txt similarity index 100% rename from doc/maia_pytree/presets.rst rename to docs/1.3/_sources/maia_pytree/presets.rst.txt diff --git a/doc/maia_pytree/pytree_module.rst b/docs/1.3/_sources/maia_pytree/pytree_module.rst.txt similarity index 100% rename from doc/maia_pytree/pytree_module.rst rename to docs/1.3/_sources/maia_pytree/pytree_module.rst.txt diff --git a/doc/maia_pytree/search.rst b/docs/1.3/_sources/maia_pytree/search.rst.txt similarity index 100% rename from doc/maia_pytree/search.rst rename to docs/1.3/_sources/maia_pytree/search.rst.txt diff --git a/doc/quick_start.rst b/docs/1.3/_sources/quick_start.rst.txt similarity index 100% rename from doc/quick_start.rst rename to docs/1.3/_sources/quick_start.rst.txt diff --git a/docs/1.3/_sources/related_projects.rst.txt b/docs/1.3/_sources/related_projects.rst.txt new file mode 100644 index 00000000..570528b9 --- /dev/null +++ b/docs/1.3/_sources/related_projects.rst.txt @@ -0,0 +1,28 @@ +.. _related: + +################ +Related projects +################ + +CGNS +---- + +The CFD General Notation System (CGNS) provides a general, portable, and extensible standard +for the storage and retrieval of computational fluid dynamics (CFD) analysis data. + +https://cgns.github.io/ + +ParaDiGM +-------- + +The ParaDiGM library provide the developers a progressive framework, which consists of a set of +low-, mid- and high-level services helpfull to write scientific +computing software that rely on a mesh. + +Cassiopée +--------- + +A set of python modules for pre- and post-processing of CFD computations + +http://elsa.onera.fr/Cassiopee/ + diff --git a/doc/releases/release_notes.rst b/docs/1.3/_sources/releases/release_notes.rst.txt similarity index 100% rename from doc/releases/release_notes.rst rename to docs/1.3/_sources/releases/release_notes.rst.txt diff --git a/doc/user_manual/algo.rst b/docs/1.3/_sources/user_manual/algo.rst.txt similarity index 100% rename from doc/user_manual/algo.rst rename to docs/1.3/_sources/user_manual/algo.rst.txt diff --git a/docs/1.3/_sources/user_manual/config.rst.txt b/docs/1.3/_sources/user_manual/config.rst.txt new file mode 100644 index 00000000..bb948961 --- /dev/null +++ b/docs/1.3/_sources/user_manual/config.rst.txt @@ -0,0 +1,53 @@ +Configuration +============= + + +Logging +------- + +Maia provides informations to the user through the loggers summarized +in the following table: + ++--------------+-----------------------+---------------------------+ +| Logger | Purpose | Default printer | ++==============+=======================+===========================+ +| maia | Light general info | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-warnings| Warnings | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-errors | Errors | mpi_rank_0_stdout_printer | ++--------------+-----------------------+---------------------------+ +| maia-stats | More detailed timings | No output | +| | and memory usage | | ++--------------+-----------------------+---------------------------+ + +The easiest way to change this default configuration is to +set the environment variable ``LOGGING_CONF_FILE`` to provide a file +(e.g. logging.conf) looking like this: + +.. code-block:: Text + + maia : mpi_stdout_printer # All ranks output to sdtout + maia-warnings : # No output + maia-errors : mpi_rank_0_stderr_printer # Rank 0 output to stderr + maia-stats : file_printer("stats.log") # All ranks output in the file + +See :ref:`logging` for a full description of available +printers or for instructions on how to create your own printers. +This page also explain how to dynamically change printers +directly in your Python application. + +Exception handling +------------------ + +Maia automatically override the `sys.excepthook +`_ +function to call ``MPI_Abort`` when an uncatched exception occurs. +This allow us to terminate the MPI execution and to avoid some deadlocks +if an exception is raised by a single process. +Note that this call force the global ``COMM_WORLD`` communicator to abort which +can have undesired effects if sub communicators are used. + +This behaviour can be disabled with a call to +``maia.excepthook.disable_mpi_excepthook()``. + diff --git a/docs/1.3/_sources/user_manual/factory.rst.txt b/docs/1.3/_sources/user_manual/factory.rst.txt new file mode 100644 index 00000000..7ad1fa42 --- /dev/null +++ b/docs/1.3/_sources/user_manual/factory.rst.txt @@ -0,0 +1,128 @@ +Factory module +============== + +The ``maia.factory`` module can be used to generate parallel CGNS trees. Those can +be obtained from an other kind of tree, or can be generated from user parameters. + +Generation +---------- + +Generation functions create a distributed tree from some parameters. + +.. autofunction:: maia.factory.generate_dist_points +.. autofunction:: maia.factory.generate_dist_block +.. autofunction:: maia.factory.generate_dist_sphere + +Partitioning +------------ + +.. autofunction:: maia.factory.partition_dist_tree + +Partitioning options +^^^^^^^^^^^^^^^^^^^^ +Partitioning can be customized with the following keywords arguments: + +.. py:attribute:: graph_part_tool + + Method used to split unstructured blocks. Irrelevent for structured blocks. + + :Admissible values: + - ``parmetis``, ``ptscotch`` : graph partitioning methods, + - ``hilbert`` : geometric method (only for NGon connectivities), + - ``gnum`` : cells are dispatched according to their absolute numbering. + + :Default value: ``parmetis``, if installed; else ``ptscotch``, if installed; ``hilbert`` otherwise. + +.. py:attribute:: zone_to_parts + + Control the number, size and repartition of partitions. See :ref:`user_man_part_repartition`. + + :Default value: Computed such that partitioning is balanced using + :func:`maia.factory.partitioning.compute_balanced_weights`. + +.. py:attribute:: part_interface_loc + + :cgns:`GridLocation` for the created partitions interfaces. Pre-existing interface keep their original location. + + :Admissible values: ``FaceCenter``, ``Vertex`` + :Default value: ``FaceCenter`` for unstructured zones with NGon connectivities; ``Vertex`` otherwise. + +.. py:attribute:: reordering + + Dictionnary of reordering options, which are used to renumber the entities in each partitioned zone. See + corresponding documentation. + +.. py:attribute:: preserve_orientation + + If True, the created interface faces are not reversed and keep their original orientation. Consequently, + NGonElements can have a zero left parent and a non zero right parent. + Only relevant for U/NGon partitions. + + :Default value: ``False`` + +.. py:attribute:: dump_pdm_output + + If True, dump the raw arrays created by paradigm in a :cgns:`CGNSNode` at (partitioned) zone level. For debug only. + + :Default value: ``False`` + +.. _user_man_part_repartition: + +Repartition +^^^^^^^^^^^ + +The number, size, and repartition (over the processes) of the created partitions is +controlled through the ``zone_to_parts`` keyword argument: each process must provide a +dictionary associating the path of every distributed zone to a list of floats. +For a given distributed zone, current process will receive as many partitions +as the length of the list (missing path or empty list means no partitions); the size of these partitions +corresponds to the values in the list (expressed in fraction of the input zone). +For a given distributed zone, the sum of all the fractions across all the processes must +be 1. + +This dictionary can be created by hand; for convenience, Maia provides three functions in the +:mod:`maia.factory.partitioning` module to create this dictionary. + +.. autofunction:: maia.factory.partitioning.compute_regular_weights +.. autofunction:: maia.factory.partitioning.compute_balanced_weights +.. autofunction:: maia.factory.partitioning.compute_nosplit_weights + +Reordering options +^^^^^^^^^^^^^^^^^^ + +For unstructured zones, the reordering options are transmitted to ParaDiGM in order to +control the renumbering of mesh entities in the partitions. + ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| Kwarg | Admissible values | Effect | Default | ++====================+===================================+=====================================+============================+ +| cell_renum_method | "NONE", "RANDOM", "HILBERT", | Renumbering method for the cells | NONE | +| | "CUTHILL", "CACHEBLOCKING", | | | +| | "CACHEBLOCKING2", "HPC" | | | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| face_renum_method | "NONE", "RANDOM", "LEXICOGRAPHIC" | Renumbering method for the faces | NONE | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| vtx_renum_method | "NONE", "SORT_INT_EXT" | Renumbering method for the vertices | NONE | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| n_cell_per_cache | Integer >= 0 | Specific to cacheblocking | 0 | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| n_face_per_pack | Integer >= 0 | Specific to cacheblocking | 0 | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ +| graph_part_tool | "parmetis", "ptscotch", | Graph partitioning library to | Same as partitioning tool | +| | "hyperplane" | use for renumbering | | ++--------------------+-----------------------------------+-------------------------------------+----------------------------+ + +Recovering from partitions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: maia.factory.recover_dist_tree + +Managing full trees +------------------- + +Almost no function in maia supports :ref:`full (not distributed) CGNS trees`, +but they can be useful for compatibility with sequential libraries. + +.. autofunction:: maia.factory.full_to_dist_tree +.. autofunction:: maia.factory.dist_to_full_tree + diff --git a/docs/1.3/_sources/user_manual/io.rst.txt b/docs/1.3/_sources/user_manual/io.rst.txt new file mode 100644 index 00000000..d95b77a3 --- /dev/null +++ b/docs/1.3/_sources/user_manual/io.rst.txt @@ -0,0 +1,81 @@ +File management +=============== + +Maia supports HDF5/CGNS file reading and writing, +see `related documention `_. + +The IO functions are provided by the ``maia.io`` module. All the high level functions +accepts a ``legacy`` parameter used to control the low level CGNS-to-hdf driver: + +- if ``legacy==False`` (default), hdf calls are performed by the python module + `h5py `_. +- if ``legacy==True``, hdf calls are performed by + `Cassiopee.Converter `_ module. + +The requested driver should be installed on your computer as well as the +hdf5 library compiled with parallel support. + +.. _user_man_dist_io: + +Distributed IO +-------------- + +Distributed IO is the privileged way to deal with CGNS files within your maia workflows. +Files are loaded as distributed trees, and, inversely, distributed trees can be written +into a single CGNS file. + +High level IO operations can be performed with the two following functions, which read +or write all data they found : + +.. autofunction:: maia.io.file_to_dist_tree +.. autofunction:: maia.io.dist_tree_to_file + + +The example below shows how to uses these high level functions: + +.. literalinclude:: snippets/test_io.py + :start-after: #file_to_dist_tree_full@start + :end-before: #file_to_dist_tree_full@end + :dedent: 2 + +Finer control of what is written or loaded can be achieved with the following steps: + +- For a **write** operation, the easiest way to write only some nodes in + the file is to remove the unwanted nodes from the distributed tree. +- For a **read** operation, the load has to be divided into the following steps: + + - Loading a size_tree: this tree has only the shape of the distributed data and + not the data itself. + - Removing unwanted nodes in the size tree; + - Fill the filtered tree from the file. + +The example below illustrate how to filter the written or loaded nodes: + +.. literalinclude:: snippets/test_io.py + :start-after: #file_to_dist_tree_filter@start + :end-before: #file_to_dist_tree_filter@end + :dedent: 2 + +Partitioned IO +-------------- + +In some cases, it may be useful to write or read a partitioned tree (keeping the +partitioned zones separated). This can be achieved using the following functions: + +.. autofunction:: maia.io.part_tree_to_file +.. autofunction:: maia.io.file_to_part_tree + +.. _user_man_raw_io: + +Raw IO +------ + +For debug purpose, trees can also be read or written independently in a +sequential manner. Be aware that information added by maia such as Distribution +or GlobalNumbering nodes will not be removed. + +.. autofunction:: maia.io.read_tree +.. autofunction:: maia.io.read_links +.. autofunction:: maia.io.write_tree +.. autofunction:: maia.io.write_trees + diff --git a/docs/1.3/_sources/user_manual/transfer.rst.txt b/docs/1.3/_sources/user_manual/transfer.rst.txt new file mode 100644 index 00000000..4b5d7895 --- /dev/null +++ b/docs/1.3/_sources/user_manual/transfer.rst.txt @@ -0,0 +1,92 @@ +Transfer module +=============== + +The ``maia.transfer`` contains functions that exchange data between the +partitioned and distributed meshes. + +Fields transfer +--------------- + +High level APIs allow to exchange data at CGNS :cgns:`Tree` or :cgns:`Zone` level. +All the provided functions take similar parameters: a distributed tree (resp. zone), +the corresponding partitioned tree (resp. list of zones), the MPI communicator and +optionally a filtering parameter. + +The following kind of data are supported: +:cgns:`FlowSolution_t`, :cgns:`DiscreteData_t`, :cgns:`ZoneSubRegion_t` and :cgns:`BCDataSet_t`. + +When transferring from distributed meshes to partitioned meshes, fields are supposed +to be known on the source mesh across all the ranks (according to :ref:`disttree definition`). +Geometric patches (such as ZoneSubRegion or BCDataSet) must exists on the relevant partitions, meaning +that only fields are transfered. + +When transferring from partitioned meshes to distributed meshes, geometric patches may or may not exist on the +distributed zone: they will be created if needed. This allows to transfer fields that have been created +on the partitioned meshes. +It is however assumed that global data (*e.g.* FlowSolution) are defined on every partition. + +Tree level +^^^^^^^^^^ + +All the functions of this section operate inplace and require the following parameters: + +- **dist_tree** (*CGNSTree*) -- Distributed CGNS Tree +- **part_tree** (*CGNSTree*) -- Corresponding partitioned CGNS Tree +- **comm** (*MPIComm*) -- MPI communicator + +.. autofunction:: maia.transfer.dist_tree_to_part_tree_all +.. autofunction:: maia.transfer.part_tree_to_dist_tree_all + +In addition, the next two methods expect the parameter **labels** (*list of str*) +which allow to pick one or more kind of data to transfer from the supported labels. + +.. autofunction:: maia.transfer.dist_tree_to_part_tree_only_labels +.. autofunction:: maia.transfer.part_tree_to_dist_tree_only_labels + +Zone level +^^^^^^^^^^ + +All the functions of this section operate inplace and require the following parameters: + +- **dist_zone** (*CGNSTree*) -- Distributed CGNS Zone +- **part_zones** (*list of CGNSTree*) -- Corresponding partitioned CGNS Zones +- **comm** (*MPIComm*) -- MPI communicator + +In addition, filtering is possible with the use of the +**include_dict** or **exclude_dict** dictionaries. These dictionaries map +each supported label to a list of cgns paths to include (or exclude). Paths +starts from the ``Zone_t`` node and ends at the targeted ``DataArray_t`` node. +Wildcard ``*`` are allowed in paths : for example, considering the following tree +structure, + +.. code:: + + Zone (Zone_t) + ├── FirstSolution (FlowSolution_t) + │   ├── Pressure (DataArray_t) + │   ├── MomentumX (DataArray_t) + │   └── MomentumY (DataArray_t) + ├── SecondSolution (FlowSolution_t) + │   ├── Pressure (DataArray_t) + │   ├── MomentumX (DataArray_t) + │   └── MomentumY (DataArray_t) + └── SpecialSolution (FlowSolution_t) +    ├── Density (DataArray_t) +    └── MomentumZ (DataArray_t) + +| ``"FirstSolution/Momentum*"`` maps to ``["FirstSolution/MomentumX", "FirstSolution/MomentumY"]``, +| ``"*/Pressure`` maps to ``["FirstSolution/Pressure", "SecondSolution/Pressure"]``, and +| ``"S*/M*"`` maps to ``["SecondSolution/MomentumX", "SecondSolution/MomentumY", "SpecialSolution/MomentumZ"]``. + +For convenience, we also provide the magic path ``['*']`` meaning "everything related to this +label". + +Lastly, we use the following rules to manage missing label keys in dictionaries: + + - For _only functions, we do not transfer any field related to the missing labels; + - For _all functions, we do transfer all the fields related to the missing labels. + +.. autofunction:: maia.transfer.dist_zone_to_part_zones_only +.. autofunction:: maia.transfer.part_zones_to_dist_zone_only +.. autofunction:: maia.transfer.dist_zone_to_part_zones_all +.. autofunction:: maia.transfer.part_zones_to_dist_zone_all diff --git a/docs/1.3/_sources/user_manual/user_manual.rst.txt b/docs/1.3/_sources/user_manual/user_manual.rst.txt new file mode 100644 index 00000000..aedb0917 --- /dev/null +++ b/docs/1.3/_sources/user_manual/user_manual.rst.txt @@ -0,0 +1,49 @@ +.. _user_manual: + +########### +User Manual +########### + +Maia methods are accessible through three main modules : + +- **Factory** allows to generate Maia trees, generally from another kind of tree + (e.g. the partitioning operation). Factory functions return a new tree + whose nature generally differs from the input tree. + +- **Algo** is the main Maia module and provides parallel algorithms to be applied on Maia trees. + Some algorithms are only available for :ref:`distributed trees ` + and some are only available for :ref:`partitioned trees `. + A few algorithms are implemented for both kind + of trees and are thus directly accessible through the :ref:`algo ` module. + + Algo functions either modify their input tree inplace, or return some data, but they do not change the nature + of the tree. + +- **Transfer** is a small module allowing to transfer data between Maia trees. A transfer function operates + on two existing trees and enriches the destination tree with data fields of the source tree. + +Using Maia trees in your application often consists in chaining functions from these different modules. + +.. image:: ./images/workflow.svg + +A typical workflow could be: + +1. Load a structured tree from a file, which produces a **dist tree**. +2. Apply some distributed algorithms to this tree: for example a structured to unstructured conversion (``algo.dist`` module). +3. Generate a corresponding partitionned tree (``factory`` module). +4. Apply some partitioned algorithms to the **part tree**, such as wall distance computation (``algo.part`` module), + and even call you own tools (e.g. a CFD solver) +5. Transfer the resulting fields to the **dist tree** (``transfer`` module). +6. Save the updated dist tree to disk. + +This user manuel describes the main functions available in each module. + +.. toctree:: + :maxdepth: 1 + :hidden: + + config + io + factory + algo + transfer diff --git a/docs/1.3/_static/basic.css b/docs/1.3/_static/basic.css new file mode 100644 index 00000000..bf18350b --- /dev/null +++ b/docs/1.3/_static/basic.css @@ -0,0 +1,906 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/1.3/_static/css/badge_only.css b/docs/1.3/_static/css/badge_only.css new file mode 100644 index 00000000..e380325b --- /dev/null +++ b/docs/1.3/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/1.3/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/1.3/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/1.3/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/1.3/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/1.3/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/1.3/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/1.3/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/1.3/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/1.3/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/1.3/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/1.3/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/1.3/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/1.3/_static/css/fonts/fontawesome-webfont.eot b/docs/1.3/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/docs/1.3/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/1.3/_static/css/fonts/fontawesome-webfont.svg b/docs/1.3/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/docs/1.3/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/1.3/_static/css/fonts/fontawesome-webfont.ttf b/docs/1.3/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/docs/1.3/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/1.3/_static/css/fonts/fontawesome-webfont.woff b/docs/1.3/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/docs/1.3/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/1.3/_static/css/fonts/fontawesome-webfont.woff2 b/docs/1.3/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/docs/1.3/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/1.3/_static/css/fonts/lato-bold-italic.woff b/docs/1.3/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/1.3/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/1.3/_static/css/fonts/lato-bold-italic.woff2 b/docs/1.3/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/1.3/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/1.3/_static/css/fonts/lato-bold.woff b/docs/1.3/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/1.3/_static/css/fonts/lato-bold.woff differ diff --git a/docs/1.3/_static/css/fonts/lato-bold.woff2 b/docs/1.3/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/1.3/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/1.3/_static/css/fonts/lato-normal-italic.woff b/docs/1.3/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/1.3/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/1.3/_static/css/fonts/lato-normal-italic.woff2 b/docs/1.3/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/1.3/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/1.3/_static/css/fonts/lato-normal.woff b/docs/1.3/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/1.3/_static/css/fonts/lato-normal.woff differ diff --git a/docs/1.3/_static/css/fonts/lato-normal.woff2 b/docs/1.3/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/1.3/_static/css/fonts/lato-normal.woff2 differ diff --git a/doc/_static/css/read_the_docs_custom.css b/docs/1.3/_static/css/read_the_docs_custom.css similarity index 100% rename from doc/_static/css/read_the_docs_custom.css rename to docs/1.3/_static/css/read_the_docs_custom.css diff --git a/docs/1.3/_static/css/theme.css b/docs/1.3/_static/css/theme.css new file mode 100644 index 00000000..8cd4f101 --- /dev/null +++ b/docs/1.3/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li span.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li span.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li span.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li span.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li span.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p.caption .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.btn .wy-menu-vertical li span.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p.caption .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.nav .wy-menu-vertical li span.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p.caption .btn .headerlink,.rst-content p.caption .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li span.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol li,.rst-content ol.arabic li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content ol.arabic li p:last-child,.rst-content ol.arabic li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover span.toctree-expand,.wy-menu-vertical li.on a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand{display:block;font-size:.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp{user-select:none;pointer-events:none}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink{visibility:hidden;font-size:14px}.rst-content .code-block-caption .headerlink:after,.rst-content .toctree-wrapper>p.caption .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content p.caption .headerlink:after,.rst-content table>caption .headerlink:after{content:"\f0c1";font-family:FontAwesome}.rst-content .code-block-caption:hover .headerlink:after,.rst-content .toctree-wrapper>p.caption:hover .headerlink:after,.rst-content dl dt:hover .headerlink:after,.rst-content h1:hover .headerlink:after,.rst-content h2:hover .headerlink:after,.rst-content h3:hover .headerlink:after,.rst-content h4:hover .headerlink:after,.rst-content h5:hover .headerlink:after,.rst-content h6:hover .headerlink:after,.rst-content p.caption:hover .headerlink:after,.rst-content table>caption:hover .headerlink:after{visibility:visible}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl dt span.classifier:before{content:" : "}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code,html.writer-html4 .rst-content dl:not(.docutils) tt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/1.3/_static/doctools.js b/docs/1.3/_static/doctools.js new file mode 100644 index 00000000..e509e483 --- /dev/null +++ b/docs/1.3/_static/doctools.js @@ -0,0 +1,326 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + var url = new URL(window.location); + url.searchParams.delete('highlight'); + window.history.replaceState({}, '', url); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + break; + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + break; + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/1.3/_static/documentation_options.js b/docs/1.3/_static/documentation_options.js new file mode 100644 index 00000000..2fa8c97f --- /dev/null +++ b/docs/1.3/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/1.3/_static/file.png b/docs/1.3/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/docs/1.3/_static/file.png differ diff --git a/docs/1.3/_static/fonts/Lato/lato-bold.eot b/docs/1.3/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 00000000..3361183a Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/1.3/_static/fonts/Lato/lato-bold.ttf b/docs/1.3/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 00000000..29f691d5 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/1.3/_static/fonts/Lato/lato-bold.woff b/docs/1.3/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/1.3/_static/fonts/Lato/lato-bold.woff2 b/docs/1.3/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/1.3/_static/fonts/Lato/lato-bolditalic.eot b/docs/1.3/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 00000000..3d415493 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/1.3/_static/fonts/Lato/lato-bolditalic.ttf b/docs/1.3/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 00000000..f402040b Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/1.3/_static/fonts/Lato/lato-bolditalic.woff b/docs/1.3/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/1.3/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/1.3/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/1.3/_static/fonts/Lato/lato-italic.eot b/docs/1.3/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 00000000..3f826421 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/1.3/_static/fonts/Lato/lato-italic.ttf b/docs/1.3/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 00000000..b4bfc9b2 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/1.3/_static/fonts/Lato/lato-italic.woff b/docs/1.3/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/1.3/_static/fonts/Lato/lato-italic.woff2 b/docs/1.3/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/1.3/_static/fonts/Lato/lato-regular.eot b/docs/1.3/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 00000000..11e3f2a5 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/1.3/_static/fonts/Lato/lato-regular.ttf b/docs/1.3/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 00000000..74decd9e Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/1.3/_static/fonts/Lato/lato-regular.woff b/docs/1.3/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/1.3/_static/fonts/Lato/lato-regular.woff2 b/docs/1.3/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/1.3/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 00000000..79dc8efe Binary files /dev/null and b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 00000000..df5d1df2 Binary files /dev/null and b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 00000000..2f7ca78a Binary files /dev/null and b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 00000000..eb52a790 Binary files /dev/null and b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/1.3/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/1.3/_static/graphviz.css b/docs/1.3/_static/graphviz.css new file mode 100644 index 00000000..19e7afd3 --- /dev/null +++ b/docs/1.3/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/docs/1.3/_static/jquery-3.5.1.js b/docs/1.3/_static/jquery-3.5.1.js new file mode 100644 index 00000000..50937333 --- /dev/null +++ b/docs/1.3/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Algorithms description

+

This section provides a detailed description of some algorithms.

+ +
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/developer_manual/algo_description/elements_to_ngons.html b/docs/1.3/developer_manual/algo_description/elements_to_ngons.html new file mode 100644 index 00000000..f451050c --- /dev/null +++ b/docs/1.3/developer_manual/algo_description/elements_to_ngons.html @@ -0,0 +1,507 @@ + + + + + + + + + + elements_to_ngons — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

elements_to_ngons

+
+

Description

+
maia.algo.dist.elements_to_ngons(dist_tree_elts, comm)
+
+
+

Take a distributed CGNSTree_t or CGNSBase_t, and transform it into a distributed NGon/NFace mesh. The tree is modified in-place.

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD, stable_sort=True)
+
+
+
+
+

Arguments

+
+
dist_tree_elts
+
a distributed tree with
    +
  • unstructured zones that contain regular element sections (i.e. not NGON, not NFACE and not MIXED)

  • +
  • the Maia tree property

  • +
+
+
+
+
comm

a communicator over which elements_to_ngons is called

+
+
+
+
+

Output

+

The tree is modified in-place. The regular element sections are replaced by:

+
    +
  • a NGon section with:

    +
      +
    • An ElementStartOffset/ElementConnectivity node describing:

      +
        +
      • first the external faces in exactly the same order as they were (in particular, gathered by face type)

      • +
      • then the internal faces, also gathered by face type

      • +
      +
    • +
    • a ParentElements node and a ParentElementsPosition node

    • +
    +
  • +
  • a NFace section with cells ordered as they were, with ids only shifted by the number of interior faces

  • +
+
+
+

Parallelism

+

elements_to_ngons is a collective function.

+
+
+

Complexity

+

With \(N\) the number of zones, \(n_i\) the number of elements of zone \(i\), \(n_{f,i}\) its number of interior faces, and \(K\) the number of processes

+
+
Sequential time

\(\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i \cdot log(n_i) \right)\)

+

The cost is dominated by the sorting of faces (interior and exterior) used to spot identical ones.

+
+
Parallel time

\(\displaystyle \mathcal{O} \left( \sum_{i=0}^N n_i/K \cdot log(n_i) \right)\)

+

The parallel distributed sort algorithm consists of three steps:

+
+
    +
  1. A partitioning step that locally gather faces into \(K\) buckets that are of equal global size. This uses a \(K\)-generalized variant of quickselect that is of complexity \(\displaystyle \mathcal{O} \left( n_i/K \cdot log(K) \right)\)

  2. +
  3. An all_to_all exchange step that gather the \(K\) buckets on the \(K\) processes. This step is not accounted for here (see below)

  4. +
  5. A local sorting step that is \(\displaystyle \mathcal{O} \left( n_i/K \cdot log(n_i/K) \right)\)

  6. +
+
+

If we sum up step 1 and 3, we get

+
+
+
+\[\begin{split}\begin{equation} \label{eq1} +\begin{split} + \textrm{C}_\textrm{parallel_sort} & = \mathcal{O} \left( n_i/K \cdot log(K) + n_i/K \cdot log(n_i/K) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot \left( log(K) + log(n_i/K) \right) \right) \\ + & = \mathcal{O} \left( n_i/K \cdot log(n_i) \right) +\end{split} +\end{equation}\end{split}\]
+
+
Theoretical scaling

\(\textrm{Speedup} = K\)

+

Experimentally, the scaling is much worse - under investigation.

+

Note: the speedup is computed by \(\textrm{Speedup} = t_s / t_p\) where \(t_s\) is the sequential time and \(t_p\) the parallel time. A speedup of \(K\) is perfect, a speedup lower than \(1\) means that sequential execution is faster.

+
+
Peak memory

Approximately \(\displaystyle \sum_{i=0}^N 2 n_i + n_{f,i}\)

+

This is the size of the input tree + the output tree. \(n_i\) is counted twice: once for the input element connectivity, once for the output NFace connectivity

+
+
Size of communications

Approximately \(\displaystyle \sum_{i=0}^N 3 n_{f,i} + n_i\) all_to_all calls

+

For each zone, one all_to_all call to sort interior faces, one to send back the faces to the NFace, one to concatenate all faces, one to concatenate all cells

+
+
Number of communication calls

Should be \(\displaystyle \mathcal{O} \left( \sum_{i=0}^N log(n_i/K) \right)\)

+

The number of communications is constant, except for the algorithm finding a balanced distribution of interior faces

+
+
Note

In practice, \(n_{f,i}\) varies from \(2 n_i\) (tet-dominant meshes) to \(3 n_i\) (hex-dominant meshes).

+
+
+
+
+

Algorithm explanation

+
maia::elements_to_ngons(cgns::tree& dist_zone, MPI_Comm comm)
+
+
+

The algorithm is divided in two steps:

+
    +
  1. Generate interior faces, insert them between exterior faces and cells. During the process, also compute the parent information (ParentElements and ParentElementsPosition) and add the CellFace connectivity to cells. All elements keep their standard element types, in particular interior faces are inserted by section type (e.g. there will be a TRI_3_interior node and a QUAD_4_interior node, not a NGon node)

  2. +
  3. Transform all sections into NGon/NFace

  4. +
+
+

Generate interior faces

+
+

Simplified algorithm

+

Let us look at a simplified sequential algorithm first:

+
1. For all element sections (3D and 2D):
+  Generate faces
+    => FaceVtx arrays (one for Tris, one for Quads)
+    => Associated Parent and ParentPosition arrays
+        (only one parent by element at this stage)
+  Interior faces are generated twice (by their two parent cells)
+  Exterior faces are generated twice (by a parent cell and a boundary face)
+
+2. For each element kind in {Tri,Quad}
+  Sort the FaceVtx array
+    (ordering: lexicographical comparison of vertices)
+  Sort the parent arrays accordingly
+    => now each face appears consecutively exactly twice
+        for interior faces,
+          the FaceVtx is always inverted between the two occurences
+        for exterior faces,
+          it depends on the normal convention
+
+Note that interior and exterior faces can be distinguised
+  by looking at the id of their parents
+
+3. For each interior face appearing twice:
+  Create an interior face section where:
+    the first FaceVtx is kept
+    the two parents are stored
+4. For each exterior face appearing twice:
+  One of the face is the original boundary section face,
+    one was generated from the joint cell
+  Send back to the original boundary face its parent face id and position
+    => store the parent information of the boundary face
+
+
+
+
+

Parallel algorithm

+

The algorithm is very similar to the sequential one. We need to modify two operations:

+
+
Sorting of the FaceVtx array (step 2)

The parallel sorting is done in three steps:

+
    +
  1. apply a partial sort std_e::sort_by_rank that will determine the rank of each FaceVtx

  2. +
  3. call an all_to_all communication step that sends each connectivity to its rank, based on the information of the previous step

  4. +
  5. sort each received FaceVtx locally

  6. +
+
+
Send back boundary parents and position to the original boundary faces (step 4)

Since the original face can be remote, this is a parallel communication operation using std_e::scatter

+
+
+
+
+

Computation of the CellFace

+

After step 2, we have all the faces exactly once, with their parent ids and parent positions. +We can then compute the CellFace of each cell section by the following algorithm:

+
For each cell section:
+  pre-allocate the CellFace array
+    (its size is n_cell_in_section * n_face_of_cell_type)
+  view it as a global distributed array
+For each unique face:
+  For each of its parent cells (could be one or two):
+    send the parent cell the id of the face and its position
+    insert the result in the CellFace array
+
+
+

As previously, the send operation uses a scatter pattern

+
+
+
+

Transform all sections into NGon/NFace

+

Thanks to the previous algorithm, we have:

+
    +
  • all exterior and interior faces with their parent information

  • +
  • the CellFace connectivity of the cell sections

  • +
+

Elements are ordered in something akin to this:

+
    +
  • boundary tris

  • +
  • boundary quads

  • +
  • internal tris

  • +
  • internal quads

  • +
  • tetras

  • +
  • pyras

  • +
  • prisms

  • +
  • hexas

  • +
+

The algorithm then just needs to:

+
    +
  • concatenate all FaceVtx of the faces into a NGon node and add a ElementStartOffset

  • +
  • concatenate all CellFace of the cells into a NFace node and add a ElementStartOffset

  • +
+
+
+
+

Design alternatives

+

The final step of the computation involves concatenating faces and cells global section by global section. This requires two heavyweight all_to_all calls. An alternative would be to concatenate locally. This would imply two trade-offs:

+
    +
  • the faces and cells would then not be globally gathered by type, and the exterior faces would not be first

  • +
  • all the PointLists (including those where GridLocation=FaceCenter) would have to be shifted

  • +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/developer_manual/developer_manual.html b/docs/1.3/developer_manual/developer_manual.html new file mode 100644 index 00000000..54ebb4d1 --- /dev/null +++ b/docs/1.3/developer_manual/developer_manual.html @@ -0,0 +1,275 @@ + + + + + + + + + + Developer Manual — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/developer_manual/logging.html b/docs/1.3/developer_manual/logging.html new file mode 100644 index 00000000..9d64b3e4 --- /dev/null +++ b/docs/1.3/developer_manual/logging.html @@ -0,0 +1,380 @@ + + + + + + + + + + Log management — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Log management

+
+

Loggers

+

A logger is a global object where an application or a library can log to. +It can be declared with

+
from maia.utils.logging import add_logger
+
+add_logger("my_logger")
+
+
+

or with the equivalent C++ code

+
#include "std_e/logging/log.hpp"
+
+std_e::add_logger("my_logger");
+
+
+

A logger declared in Python is available in C++ and vice-versa: we do not need to create it twice.

+
+

Note

+

Loggers are available to pure C/C++ programs (in particular, test programs that do not use the Python interpreter).

+
+

It can then be referred to by its name. If we want to log a string to my_logger, we will do it like so:

+
from maia.utils.logging import log
+
+log('my_logger', 'my message')
+
+
+
#include "std_e/logging/log.hpp"
+
+std_e::log("my_logger", "my message");
+
+
+

Typically, an application will use several loggers to log messages by themes. By convention, the name of a logger in the application my_app should begin with my_app. For instance, loggers can be named: my_app, my_app-stats, my_app-errors

+

Loggers are both

+
    +
  • developper-oriented, that is, when a developper wants to output a message, he should select a logger (existing or not) encompassing the theme of the message he wants to log,

  • +
  • user-oriented, because a user can choose what logger he wants to listen to.

  • +
+
+
+

Printers

+

By itself, a logger does not do anything with the messages it receives. For that, we need to attach printers to a logger that will handle its messages.

+

For instance, we can attach a printer that will output the message to the console:

+
from maia.utils.logging import add_printer_to_logger
+add_printer_to_logger('my_logger','stdout_printer')
+add_printer_to_logger('my_logger','file_printer("my_file.log")')
+
+
+

Printers are user-oriented: it is the user of the application who decides what he wants to do with the message that each logger is receiving, by attaching none or several printers to a logger.

+
+

Available printers

+
+
stdout_printer, stderr_printer

output messages to the console (respectively stdout and stderr)

+
+
mpi_stdout_printer, mpi_stderr_printer

output messages to the console, but prefix them by MPI.COMM_WORLD.Get_rank()

+
+
mpi_rank_0_stdout_printer, mpi_rank_0_stderr_printer

output messages to the console, if MPI.COMM_WORLD.Get_rank()==0

+
+
file_printer(‘my_file.extension’)

output messages to file my_file.extension

+
+
mpi_file_printer(‘my_file.extension’)

output messages to files my_file.{rk}.extension, with rk = MPI.COMM_WORLD.Get_rank()

+
+
+
+

Note

+

MPI-aware printers use MPI.COMM_WORLD rather than a local communicator. While the latter would be more correct, it would imply local instanciation of printers, at the time or after a new communicator is created. While this is certainly doable, up until now we feel that it does not worth the burden. But if the need arises, they can still be added.

+
+
+
+

Create your own printer

+

Any Python type can be used as a printer as long as it provides a log method that accepts a string argument.

+
from maia.utils.logging import add_printer_to_logger
+
+class my_printer:
+  def log(self, msg):
+    print(msg)
+
+add_printer_to_logger('my_logger',my_printer())
+
+
+
+
+
+

Configuration file

+

Loggers are associated to default printers. While they can be configured anytime in the Python scripts, most of the time, reading a configuration file at the start of the program is enough. The program will try to read a configuration file if the environment variable LOGGING_CONF_FILE is set. A logging configuration file looks like this:

+
my_app : mpi_stdout_printer
+my_app-my_theme : mpi_file_printer('my_theme.log')
+
+
+

For developpers, a logging file logging.conf with loggers and default printers is put in the build/ folder, and LOGGING_CONF_FILE is set accordingly.

+
+
+

Maia specifics

+

Maia provides 4 convenience functions that use Maia loggers

+
from maia.utils import logging as mlog
+mlog.info('info msg') # uses the 'maia' logger
+mlog.stat('stat msg') # uses the 'maia-stats' logger
+mlog.warning('warn msg') # uses the 'maia-warnings' logger
+mlog.error('error msg') # uses the 'maia-errors' logger
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/developer_manual/maia_dev/conventions.html b/docs/1.3/developer_manual/maia_dev/conventions.html new file mode 100644 index 00000000..b71ff60b --- /dev/null +++ b/docs/1.3/developer_manual/maia_dev/conventions.html @@ -0,0 +1,302 @@ + + + + + + + + + + Conventions — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Conventions

+
+

Naming conventions

+
    +
  • snake_case

  • +
  • A variable holding a number of things is written n_thing. Example: n_proc, n_vtx. The suffix is singular.

  • +
  • For unit tests, when testing variable <var>, the hard-coded expected variable is named expected_<var>.

  • +
  • Usual abbreviations

    +
      +
    • elt for element

    • +
    • vtx for vertex

    • +
    • proc for process

    • +
    • sz for size (only for local variable names, not functions)

    • +
    +
  • +
  • Connectivities

    +
      +
    • cell_vtx means mesh array of cells described by their vertices (CGNS example: HEXA_8)

    • +
    • cell_face means the cells described by their faces (CGNS example: NFACE_n)

    • +
    • face_cell means for each face, the two parent cells (CGNS example: ParentElement)

    • +
    • … so on: face_vtx, edge_vtx

    • +
    • elt_vtx, elt_face…: in this case, elt can be either a cell, a face or an edge

    • +
    +
  • +
+
+
+

Other conventions

+

We try to use semi-open intervals and 0-indexed structures. This is coherent with Python, C++, and their libraries, but unfortunately not with CGNS.

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/developer_manual/maia_dev/development_workflow.html b/docs/1.3/developer_manual/maia_dev/development_workflow.html new file mode 100644 index 00000000..74223d0c --- /dev/null +++ b/docs/1.3/developer_manual/maia_dev/development_workflow.html @@ -0,0 +1,297 @@ + + + + + + + + + + Development workflow — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Development workflow

+
+

Sub-modules

+

The Maia repository is compatible with the development process described here. It uses git submodules to ease the joint development with other repositories compatible with this organization.

+

TL;DR: configure the git repository by sourcing this file and then execute:

+
cd $MAIA_FOLDER
+git submodule update --init
+git_config_submodules
+
+
+
+
+

Launch tests

+

Tests can be launched by calling CTest, but during the development, we often want to parameterize how to run tests (which ones, number of processes, verbosity level…).

+

There is a source.sh generated in the build/ folder. It can be sourced in order to get the correct environment to launch the tests (notably, it updates LD_LIBRARY_PATH and PYTHONPATH to point to build artifacts).

+

Tests can be called with e.g.:

+
cd $PROJECT_BUILD_DIR
+source source.sh
+mpirun -np 4 external/std_e/std_e_unit_tests
+./external/cpp_cgns/cpp_cgns_unit_tests
+mpirun -np 4 test/maia_doctest_unit_tests
+mpirun -np 4 pytest $PROJECT_SRC_DIR/maia --with-mpi
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/genindex.html b/docs/1.3/genindex.html new file mode 100644 index 00000000..9fcb94c5 --- /dev/null +++ b/docs/1.3/genindex.html @@ -0,0 +1,795 @@ + + + + + + + + + + Index — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Index
  • + + +
  • + + + +
  • + +
+ + +
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | H + | I + | L + | M + | N + | P + | Q + | R + | S + | T + | U + | V + | W + | Z + +
+

A

+ + + +
+ +

B

+ + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

L

+ + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

P

+ + + +
+ +

Q

+ + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ +

Z

+ + + +
+ + + +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/index.html b/docs/1.3/index.html new file mode 100644 index 00000000..4c03b88a --- /dev/null +++ b/docs/1.3/index.html @@ -0,0 +1,276 @@ + + + + + + + + + + Welcome to Maia! — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Welcome to Maia!

+

Maia is a C++ and Python library for parallel algorithms over CGNS trees: distributed IO, partitioning, data management and various kind of transformations (generation of faces, destructuration, extractions, …).

+
+

Documentation summary

+

Quick start is the perfect page for a first overview of Maia or to retrieve the environments to use on Onera’s clusters.

+

Introduction details the extensions made to the CGNS standard in order to define parallel CGNS trees.

+

User Manual is the main part of this documentation. It describes most of the high level APIs provided by Maia.

+

Developer Manual (under construction) provides more details on some algorithms and can be consulted if you want to contribute to Maia.

+

The pytree module describes how to manipulate python CGNS trees. There is no parallel functionalities is this module, which may become an independent project one day.

+
+_images/logo_maia.svg +

Maia is an open source software developed at ONERA. +Associated source repository and issue tracking are hosted on Gitlab.

+
+
+
+
+
+
+ + +
+ +
+
+ + +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/installation.html b/docs/1.3/installation.html new file mode 100644 index 00000000..47e1437c --- /dev/null +++ b/docs/1.3/installation.html @@ -0,0 +1,428 @@ + + + + + + + + + + Installation — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Installation

+
+

Prefered installation procedure

+

Maia depends on quite a few libraries of different kinds, be it system libraries like MPI, third-party libraries like HDF5, ONERA libraries like ParaDiGM and Cassiopée, git submodules (std_e…), or Python modules (mpi4py, ruamel). The prefered way of installing Maia in a coherent environment is by using the Spack package manager. A Spack recipe for Maia can be found on the ONERA Spack repository.

+
+

Installation through Spack

+
    +
  1. Source a Spack repository on your machine.

  2. +
  3. If you don’t have a Spack repository ready, you can download one with git clone https://github.com/spack/spack.git. On ONERA machines, it is advised to use the Spacky helper.

  4. +
  5. Download the ONERA Spack repository with git clone https://gitlab.onera.net/informatics/infra/onera_spack_repo.git

  6. +
  7. Tell Spack that package recipes are in onera_spack_repo by adding the following lines to $SPACK_ROOT/etc/repos.yaml:

  8. +
+
repos:
+- path/to/onera_spack_repo
+
+
+

(note that spacky does steps 3. and 4. for you)

+
    +
  1. You should be able to see the package options of Maia with spack info maia

  2. +
  3. To install Maia: spack install maia

  4. +
+
+
+

Development workflow

+

For development, it is advised to use Spack to have Maia dependencies, but then follow a typical CMake workflow with cmake/make.

+
+

Dependencies

+

To get access to Maia dependencies in your development environment, you can:

+
    +
  • Install a Spack version of Maia, source it in your development environment to get all the dependencies, then override with your own compiled version of Maia

  • +
  • Do the same, but use a Spack environment containing Maia instead of just the Maia package

  • +
  • Source a Spack environment where Maia has been removed from the environment view. This can be done by adding the following lines to the spack.yaml environement file:

  • +
+
view:
+  default:
+    exclude: ['maia']
+
+
+

This last option is cleaner because you are sure that you are not using another version of Maia (but it means you need to create or have access to such an environment view)

+
+
+

Source the build folder

+

You can develop without the need to install Maia. However, in addition to sourcing your dependencies in your development environment, you also need to source the build artifacts by:

+
cd $MAIA_BUILD_FOLDER
+source source.sh
+
+
+

The source.sh file is created by CMake and will source all Maia artifacts (dynamic libraries, python modules…)

+
+
+
+

Development workflow with submodules

+

It is often practical to develop Maia with some of its dependencies, namely:

+
    +
  • project_utils

  • +
  • std_e

  • +
  • cpp_cgns

  • +
  • paradigm

  • +
  • pytest_parallel

  • +
+

For that, you need to use git submodules. Maia submodules are located at $MAIA_FOLDER/external. To populate them, use git submodule update --init. Once done, CMake will use these versions of the dependencies. If you don’t populate the submodules, CMake will try to use the ones of your environment (for instance, the one installed by Spack).

+

We advise that you use some additional submodule configuration utilities provided in this file. In particular, you should use:

+
cd $MAIA_FOLDER
+git submodule update --init
+git_config_submodules
+
+
+

The detailed meaning of git_config_submodules and the git submodule developper workflow of Maia is presented here.

+

If you are using Maia submodules, you can filter them out from your Spack environment view like so:

+
view:
+  default:
+    exclude: ['maia','std-e','cpp-cgns','paradigm','pytest_parallel']
+
+
+
+
+
+

Manual installation procedure

+
+

Dependencies

+

Maia depends on:

+
    +
  • python3

  • +
  • MPI

  • +
  • hdf5

  • +
  • Cassiopée

  • +
  • pytest >6 (python package)

  • +
  • ruamel (python package)

  • +
  • mpi4py (python package)

  • +
+

The build process requires:

+
    +
  • Cmake >= 3.14

  • +
  • GCC >= 8 (Clang and Intel should work but no CI)

  • +
+
+

Other dependencies

+

During the build process, several other libraries will be downloaded:

+
    +
  • pybind11

  • +
  • range-v3

  • +
  • doctest

  • +
  • ParaDiGM

  • +
  • project_utils

  • +
  • std_e

  • +
  • cpp_cgns

  • +
+

This process should be transparent.

+
+
+

Optional dependencies

+

The documentation build requires:

+
    +
  • Doxygen >= 1.8.19

  • +
  • Breathe >= 4.15 (python package)

  • +
  • Sphinx >= 3.00 (python package)

  • +
+
+
+
+

Build and install

+
    +
  1. Install the required dependencies. They must be in your environment (PATH, LD_LIBRARY_PATH, PYTHONPATH).

  2. +
+
+

For pytest, you may need these lines :

+
+
pip3 install --user pytest
+pip3 install --user pytest-mpi
+pip3 install --user pytest-html
+pip3 install --user pytest_check
+pip3 install --user ruamel.yaml
+
+
+
    +
  1. Then you need to populate your external folder. You can do it with git submodule update --init

  2. +
  3. Then use CMake to build maia, e.g.

  4. +
+
SRC_DIR=<path to source repo>
+BUILD_DIR=<path to tmp build dir>
+INSTALL_DIR=<path to where you want to install Maia>
+cmake -S $SRC_DIR -B$BUILD_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR
+cd $BUILD_DIR && make -j && make install
+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/introduction/introduction.html b/docs/1.3/introduction/introduction.html new file mode 100644 index 00000000..c1145b6b --- /dev/null +++ b/docs/1.3/introduction/introduction.html @@ -0,0 +1,511 @@ + + + + + + + + + + Introduction — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Introduction

+

These section introduces the core concepts of distributed and partitioned data +used in Maia, and their application to define parallel CGNS trees.

+
+

Core concepts

+
+

Dividing data

+

Global data is the complete data that describes an object. Let’s represent it as the +following ordered shapes:

+../_images/data_full.svg

Now imagine that you want to split this data into N pieces (N=3 for the next illustrations). +Among all the possibilities, we distinguishes two ways to do it:

+
    +
  1. Preserving order: we call such repartition distributed data, and we use the term block +to refer to a piece of this distributed data.

  2. +
+
+
../_images/data_dist.svg

Several distributions are possible, depending on where data is cut, but they all share the same properties:

+
+
    +
  • the original order is preserved across the distributed data,

  • +
  • each element appears in one and only one block,

  • +
  • a block can be empty as long as the global order is preserved (b).

  • +
+
+
+
    +
  1. Taking arbitrary subsets of the original data: we call such subsets partitioned data, and we use the term partition +to refer to a piece of this partitioned data.

  2. +
+
+
../_images/data_part.svg

Due to this relaxed constraint, there is much more admissible splits since we allow in our definition the following cases:

+
+
    +
  • an element can appear in several partitions, or several times within the same partition (b),

  • +
  • it is allowed that an element does not appear in a partition (c).

  • +
+
+

Such repartitions are often useful when trying to gather the elements depending on +some characteristics: on the above example, we created the partition of squared shaped elements, round shaped +elements and unfilled elements (b). Thus, some elements belong to more than one partition.

+
+

A key point is that no absolute best way of diving data: depending of what we want to do with the +data, one of the way to divide it may be more adapted. In the previous example:

+
    +
  • distributed data is fine if you want to count the number of filled shapes: you can count in each +block and then sum the result over the blocks.

  • +
  • Now assume that you want to renumber the elements depending on their shape, then on their color: +if partitioned data (b) is used, partitions 1 and 2 could independently order +their elements by color since they are already sorted by shape 1.

  • +
+
+
+

Numberings

+

In order to describe the link between our divisions and the original global data, we need to +define additional concepts.

+

For distributed data, since the original ordering is respected, the link with the global data is totally implicit: +we just need to know the number of elements in each block or, equivalently, the distribution array +of the data. This is an array of size N+1 indicating the bounds of each block. By convention, distribution +array starts at 0 and uses semi-open intervals.

+../_images/data_dist_gnum.svg

With this information, the global number of the jth element in the ith block is given by +\(\mathtt{dist[i] + j + 1}\).

+

On the contrary, for partitioned data, we have to explicitly store the link with the global data: +we use a local to global numbering array (often called LN_to_GN for short). +Each partition has its own LN_to_GN array whose size is the number of elements in the partition.

+../_images/data_part_gnum.svg

Then, the global number of the jth element in the ith partition is simply given by +\(\mathtt{LN\_to\_GN[i][j]}\).

+

For any global data, these additional informations allow to create a mapping beetween global data, partitioned +data and distributed data. Thus, it is always possible to reconstruct one of the views from another one.

+
+
+

Application to MPI parallelism

+

The concepts introduced above make all sense in the context of distributed memory computers. +In such architecture, the global data is in fact never detained by a single proc (it would be too heavy): +we always use a distributed or partitioned view of this data. +Using a good repartition is often the key of a well balanced algorithm.

+

In the distributed view, we produce as much blocks as the number of MPI processes. Each process holds +its own block, and the distribution array, of size n_rank+1, is know by each process.

+

In the partitioned view, we often produce one partition per process; but it can sometime be useful to +put several partitions on a given process. Each process holds the data associated to its partition, +including the related LN_to_GN arrays (LN_to_GN related to the other partitions +are not know by the current process).

+

The ParaDiGM library provides some low level methods to exchange data between the partitioned and +distributed views in a MPI context, with options to deal with redundant elements, create new distributions, +manage variable strides, etc.

+
+
+
+

Application to meshes

+

Until now, we have seen the definition of distributed and partitioned data for a single array +of global data. Let see how to apply these concepts to a basic CFD mesh.

+

Consider the following 12-vertices (green numbers), 6-quads (purple numbers) mesh and +assume that it is described by a cell to vertex connectivity. Thus, the mesh file defines +3 global arrays:

+
    +
  • the CoordinateX and CoordinateY arrays, each one of size 12

  • +
  • the Connectivity array of size 6*4 = 24

  • +
+../_images/full_mesh.svg

If we have 2 processes at our disposal, a parallel way to load this mesh is to +distribute all the vertex-related entities with a distribution array of [0,6,12] +and all the element-related entities with a distribution array of [0,3,6] 2:

+../_images/dist_mesh_arrays.svg

Then, the blue part of the arrays will be stored on the first process and the red part on the second process. +Looking back at the original mesh, this correspond to the following distributed meshes:

+../_images/dist_mesh.svg

with the blue entities stored on the first process, and the red ones on the second process.

+

Notice that the distributed mesh is not suited for solver computation. For instance, cells on the domain treated by the first process are not connex. Worst yet, vertex and element quantities are distributed independently. Take a look at cell 1: it is blue so it belongs to process 0. However,its vertices 9 and 10 are on process 1, this means that there coordinates are not accessible directly on process 0. For these reasons, we want to properly partition our mesh, such as this:

+../_images/part_mesh.svg../_images/part_mesh_arrays.svg

Now we have two semi-independent meshes and we can reason about each element with all its associated data +present on the same process. This partitioned view of the mesh has the two following properties:

+
+
    +
  • Coherency: every data array is addressable locally,

  • +
  • Connexity: the data represents geometrical entities that define a local subregion of the mesh.

  • +
+
+

We want to keep the link between the base mesh and its partitioned version. For that, we need to store global numbering arrays, quantity by quantity:

+../_images/dist_part_LN_to_GN.svg

For example, for sub-mesh 0, we can check that element number 3 is actually element number 6 of the original mesh. +We can also see that vertex 3 of sub-mesh 0, and vertex 1 of submesh 1 are actually the same, that is, vertex 4 of the global mesh.

+

Thanks to these global numbering, any quantity computed on the partitioned meshes (such as a vertex based field) +can then be transfered to the corresponding distributed vertex, before writting back the results.

+
+
+

Maia CGNS trees

+
+

Overview

+

Since Python/CGNS trees describe CFD meshes, we can apply the previous concepts to +define parallel trees.

+

A full tree is a tree as it is inside a CGNS file, or how it would be loaded by only one process. A full tree is global data.

+

A dist tree is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array. See Distributed trees.

+

A part tree is a partial tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process. See Partitioned trees.

+

A size tree is a tree in which only the size of the data is stored. A size tree is typically global data because each process needs it to know which block of data it will have to load and store.

+

([Legacy] A skeleton tree is a collective tree in which fields and element connectivities are not loaded)

+

As explained in previous section, the standard full tree does not make sense in the context of a MPI parallel +application: all the trees encountered within the execution of maia are distributed trees or partitioned trees. +The next section describe the specification of these trees.

+
+
+

Specification

+

Let us use the following tree as an example:

+../_images/tree_seq.png +

This tree is a full tree. It may appear like that on a HDF5/CGNS file, or if loaded entirely on one process as a Python/CGNS tree.

+
+

Distributed trees

+

A dist tree is a CGNS tree where the tree structure is replicated across all processes, but array values of the nodes are distributed, that is, each process only stores a block of the complete array.

+

If we distribute our tree over two processes, we would then have something like that:

+../_images/dist_tree.png +

Let us look at one of them and annotate nodes specific to the distributed tree:

+../_images/dist_tree_expl.png +

Arrays of non-constant size are distributed: fields, connectivities, PointLists. +Others (PointRanges, CGNSBase_t and Zone_t dimensions…) are of limited size and therefore replicated on all processes with virtually no memory penalty.

+

On each process, for each entity kind, a partial distribution is stored, that gives information of which block of the arrays are stored locally.

+

For example, for process 0, the distribution array of vertices of MyZone is located at MyBase/MyZone/Distribution/Vertex and is equal to [0, 9, 18]. It means that only indices in the semi-open interval [0 9) are stored by the dist tree on this process, and that the total size of the array is 18. +This partial distribution applies to arrays spaning all the vertices of the zone, e.g. CoordinateX.

+

More formally, a partial distribution related to an entity kind E is an array [start,end,total_size] of 3 integers where [start:end) is a closed/open interval giving, for all global arrays related to E, the sub-array that is stored locally on the distributed tree, and total_size is the global size of the arrays related to E.

+

The distributed entities are:

+
+
Vertices and Cells

The partial distribution are stored in Distribution/Vertex and Distribution/Cell nodes at the level of the Zone_t node.

+

Used for example by GridCoordinates_t and FlowSolution_t nodes if they do not have a PointList (i.e. if they span the entire vertices/cells of the zone)

+
+
Quantities described by a PointList or PointRange

The partial distribution is stored in a Distribution/Index node at the level of the PointList/PointRange

+

For example, ZoneSubRegion_t and BCDataSet_t nodes.

+

If the quantity is described by a PointList, then the PointList itself is distributed the same way (in contrast, a PointRange is fully replicated across processes because it is lightweight)

+
+
Connectivities

The partial distribution is stored in a Distribution/Element node at the level of the Element_t node. Its values are related to the elements, not the vertices of the connectivity array.

+

If the element type is heterogenous (NGon, NFace or MIXED) a Distribution/ElementConnectivity is also present, and this partial distribution is related to the ElementConnectivity array.

+
+
+
+

Note

+

A distributed tree object is not norm-compliant since most of its arrays are partial: on the previous example, +CoordinateX array on rank 0 has a length of 9 when MyZone declares 18 vertices. +However, the union of all the distributed tree objects represents a norm-compliant CGNS tree.

+
+
+
+

Partitioned trees

+

A part tree is a partial CGNS tree, i.e. a tree for which each zone is only stored by one process. Each zone is fully stored by its process.

+

If we take the full tree from before and partition it, we may get the following tree:

+../_images/part_tree.png +

If we annotate the first one:

+../_images/part_tree_expl.png +

A part tree is just a regular, norm-compliant tree with additional information (in the form of GlobalNumbering nodes) that keeps the link with the unpartitioned tree it comes from. Notice that the tree structure is not necessarily the same across all processes.

+

The GlobalNumbering nodes are located at the same positions that the Distribution nodes were in the distributed tree.

+

A GlobalNumbering contains information to link an entity in the partition to its corresponding entity in the original tree. For example, the element section Hexa has a global numbering array of value [3 4]. It means:

+
    +
  • Since it is an array of size 2, there is 2 elements in this section (which is confirmed by the ElementRange) ,

  • +
  • The first element was the element of id 3 in the original mesh,

  • +
  • The second element was element 4 in the original mesh.

  • +
+
+
+

Naming conventions

+

When partitioning, some nodes are split, so there is convention to keep track of the fact they come from the same original node:

+
    +
  • Zone_t nodes : MyZone is split in MyZone.PX.NY where X is the rank of the process, and Y is the id of the zone on process X.

  • +
  • Splitable nodes (notably GC_t) : MyNode is split in MyNode.N. They appear in the following scenario:

    +
      +
    • We partition for 3 processes

    • +
    • Zone0 is connected to Zone1 through GridConnectivity_0_to_1

    • +
    • Zone0 is not split (but goes to process 0 and becomes Zone0.P0.N0). Zone1 is split into Zone1.P1.N0 and Zone1.P2.N0. Then GridConnectivity_0_to_1 of Zone0 must be split into GridConnectivity_0_to_1.1 and GridConnectivity_0_to_1.2.

    • +
    +
  • +
+

Note that partitioning may induce new GC_t internal to the original zone being splitted. Their name is implementation-defined and those nodes do not have a GlobalNumbering since they did not exist in the original mesh.

+
+
+
+

Maia trees

+

A CGNS tree is said to be a Maia tree if it has the following properties:

+
    +
  • For each unstructured zone, the ElementRange of all Elements_t sections

    +
      +
    • are contiguous

    • +
    • are ordered by ascending dimensions (i.e. edges come first, then faces, then cells)

    • +
    • the first section starts at 1

    • +
    • there is at most one section by element type (e.g. not possible to have two QUAD_4 sections)

    • +
    +
  • +
+

Notice that this is property is required by some functions of Maia, not all of them!

+

A Maia tree may be a full tree, a distributed tree or a partitioned tree.

+

Footnotes

+
+
1
+

Actually, such algorithm could be written for distributed data, but is less intuitive and require more knowledge of what +if happening on the other blocks.

+
+
2
+

Other distributions are possible : we could, for example, affect all the CoordinateX array on the first process (using the distribution array [0,12,12]) and the CoordinateY array on the second, but we would have to manage a different distribution for each array.

+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/license.html b/docs/1.3/license.html new file mode 100644 index 00000000..e4a75c8c --- /dev/null +++ b/docs/1.3/license.html @@ -0,0 +1,680 @@ + + + + + + + + + + License — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

License

+
+

Mozilla Public License Version 2.0

+
+
+

1. Definitions

+
+
1.1. “Contributor”

means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software.

+
+
1.2. “Contributor Version”

means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor’s Contribution.

+
+
1.3. “Contribution”

means Covered Software of a particular Contributor.

+
+
1.4. “Covered Software”

means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof.

+
+
1.5. “Incompatible With Secondary Licenses”

means

+
+
+
    +
  • +
    (a) that the initial Contributor has attached the notice described

    in Exhibit B to the Covered Software; or

    +
    +
    +
  • +
  • +
    (b) that the Covered Software was made available under the terms of

    version 1.1 or earlier of the License, but not also under the +terms of a Secondary License.

    +
    +
    +
  • +
+
+
1.6. “Executable Form”

means any form of the work other than Source Code Form.

+
+
1.7. “Larger Work”

means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software.

+
+
1.8. “License”

means this document.

+
+
1.9. “Licensable”

means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License.

+
+
1.10. “Modifications”

means any of the following:

+
+
+
    +
  • +
    (a) any file in Source Code Form that results from an addition to,

    deletion from, or modification of the contents of Covered +Software; or

    +
    +
    +
  • +
  • +
    (b) any new file in Source Code Form that contains any Covered

    Software.

    +
    +
    +
  • +
+
+
1.11. “Patent Claims” of a Contributor

means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version.

+
+
1.12. “Secondary License”

means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses.

+
+
1.13. “Source Code Form”

means the form of the work preferred for making modifications.

+
+
1.14. “You” (or “Your”)

means an individual or a legal entity exercising rights under this +License. For legal entities, “You” includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, “control” means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity.

+
+
+
+
+

2. License Grants and Conditions

+
+

2.1. Grants

+

Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license:

+
    +
  • +
    (a) under intellectual property rights (other than patent or trademark)

    Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and

    +
    +
    +
  • +
  • +
    (b) under Patent Claims of such Contributor to make, use, sell, offer

    for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version.

    +
    +
    +
  • +
+
+
+

2.2. Effective Date

+

The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution.

+
+
+

2.3. Limitations on Grant Scope

+

The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor:

+
    +
  • +
    (a) for any code that a Contributor has removed from Covered Software;

    or

    +
    +
    +
  • +
  • +
    (b) for infringements caused by: (i) Your and any other third party’s

    modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or

    +
    +
    +
  • +
  • +
    (c) under Patent Claims infringed by Covered Software in the absence of

    its Contributions.

    +
    +
    +
  • +
+

This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4).

+
+
+

2.4. Subsequent Licenses

+

No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3).

+
+
+

2.5. Representation

+

Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License.

+
+
+

2.6. Fair Use

+

This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents.

+
+
+

2.7. Conditions

+

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1.

+
+
+
+

3. Responsibilities

+
+

3.1. Distribution of Source Form

+

All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients’ rights in the Source Code +Form.

+
+
+

3.2. Distribution of Executable Form

+

If You distribute Covered Software in Executable Form then:

+
    +
  • +
    (a) such Covered Software must also be made available in Source Code

    Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and

    +
    +
    +
  • +
  • +
    (b) You may distribute such Executable Form under the terms of this

    License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients’ rights in the Source Code Form under this License.

    +
    +
    +
  • +
+
+
+

3.3. Distribution of a Larger Work

+

You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s).

+
+
+

3.4. Notices

+

You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies.

+
+
+

3.5. Application of Additional Terms

+

You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction.

+
+
+
+

4. Inability to Comply Due to Statute or Regulation

+

If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it.

+
+
+

5. Termination

+

5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice.

+

5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate.

+

5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination.

+
+
+

6. Disclaimer of Warranty

+
+

Covered Software is provided under this License on an “as is” +basis, without warranty of any kind, either expressed, implied, or +statutory, including, without limitation, warranties that the +Covered Software is free of defects, merchantable, fit for a +particular purpose or non-infringing. The entire risk as to the +quality and performance of the Covered Software is with You. +Should any Covered Software prove defective in any respect, You +(not any Contributor) assume the cost of any necessary servicing, +repair, or correction. This disclaimer of warranty constitutes an +essential part of this License. No use of any Covered Software is +authorized under this License except under this disclaimer.

+
+
+
+

7. Limitation of Liability

+
+

Under no circumstances and under no legal theory, whether tort +(including negligence), contract, or otherwise, shall any +Contributor, or anyone who distributes Covered Software as +permitted above, be liable to You for any direct, indirect, +special, incidental, or consequential damages of any character +including, without limitation, damages for lost profits, loss of +goodwill, work stoppage, computer failure or malfunction, or any +and all other commercial damages or losses, even if such party +shall have been informed of the possibility of such damages. This +limitation of liability shall not apply to liability for death or +personal injury resulting from such party’s negligence to the +extent applicable law prohibits such limitation. Some +jurisdictions do not allow the exclusion or limitation of +incidental or consequential damages, so this exclusion and +limitation may not apply to You.

+
+
+
+

8. Litigation

+

Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party’s ability to bring +cross-claims or counter-claims.

+
+
+

9. Miscellaneous

+

This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor.

+
+
+

10. Versions of the License

+
+

10.1. New Versions

+

Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number.

+
+
+

10.2. Effect of New Versions

+

You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward.

+
+
+

10.3. Modified Versions

+

If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License).

+
+
+

10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses

+

If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached.

+
+
+
+

Exhibit A - Source Code Form License Notice

+
+

This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/.

+
+

If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice.

+

You may add additional accurate notices of copyright ownership.

+
+
+

Exhibit B - “Incompatible With Secondary License” Notice

+
+

This Source Code Form is “Incompatible With Secondary Licenses”, as +defined by the Mozilla Public License, v. 2.0.

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/maia_pytree/basic.html b/docs/1.3/maia_pytree/basic.html new file mode 100644 index 00000000..f40ff02f --- /dev/null +++ b/docs/1.3/maia_pytree/basic.html @@ -0,0 +1,839 @@ + + + + + + + + + + Basic node editing — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Basic node editing

+

This page describe the maia.pytree functions to read or write +the different attributes of a CGNSTree. These functions are simple +but provide the basis for complex operations.

+
+

Vocabulary

+

According to the CGNS standard, a CGNS node is a structure described by three attributes:

+
    +
  • a name, which is a case-sensitive identifier shorter than 32 characters;

  • +
  • a label, which must be taken from a predefined list;

  • +
  • a optional value, which is a (possibly multidimensional) array of data;

  • +
+

plus a list of children which are themselves nodes. Due to that, +nodes have in fact a hierarchic structure, which is why we rather employ the word +tree to refer to them.

+

The organisation of this structure (for example: what are the allowed labels +under each node, or what are the allowed values for each label) +is defined by the CGNS/SIDS description +and will not be described here.

+

In addition, the sids-to-python specification +defines how to describe a node structure in python. +maia.pytree conforms to this mapping which, in short, states that:

+
    +
  • node names and labels are simple str objects

  • +
  • node values can be either None, or a numpy F-contiguous array

  • +
  • nodes are defined by a list following the pattern [name, value, children_list, label]

  • +
+
+
+

Node edition

+

Accessing to the different attributes of a node with the [] operator +is possible but error-prone. Consequently, we advice the user to use +the available getters and setters:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

get_name(node)

Return the name of a CGNSNode

set_name(node, name)

Set the name of a CGNSNode

get_label(node)

Return the label of a CGNSNode

set_label(node, label)

Set the label of a CGNSNode

get_children(node)

Return the list of children of a CGNSNode

add_child(node, child)

Append a child node to the children list of a CGNSNode

set_children(node, children)

Set the children list of a CGNSNode

get_value(node[, raw])

Return the value of a CGNSNode

set_value(node, value)

Set the value of a CGNSNode

update_node(node[, name, label, value, children])

Update some attribute of a CGNSNode

+

Here is few examples using theses functions:

+
>>> node = ["MyNode", None, [], "UserDefinedData_t"]
+>>> PT.get_name(node)
+'MyNode'
+>>> PT.set_value(node, [1,2,3])
+>>> PT.get_value(node)
+array([1, 2, 3], dtype=int32)
+>>> pnode = ["ParentNode", np.array([3.14]), [], "UserDefinedData_t"]
+>>> PT.set_children(pnode, [node])
+>>> len(PT.get_children(pnode))
+1
+>>> PT.update_node(node, name="MyUpdatedNode")
+>>> [PT.get_name(n) for n in PT.get_children(pnode)]
+['MyUpdatedNode']
+
+
+

Similarly, although trees can be displayed with the print() function, +it is preferable to use print_tree() for a better rendering:

+
>>> print(pnode)
+['ParentNode', array([3.14]), [['MyUpdatedNode', array([1, 2, 3], dtype=int32), [], 'UserDefinedData_t']], 'UserDefinedData_t']
+>>> PT.print_tree(pnode)
+ParentNode UserDefinedData_t R8 [3.14]
+└───MyUpdatedNode UserDefinedData_t I4 [1 2 3]
+
+
+
+

See also

+

In practice, it is common to use searches +to navigate in tree hierarchie and inspection to get +relevant data.

+
+
+
+

Node creation

+

In the same idea of avoiding to manipulate the underlying list by hand, the following functions +can be used to create new nodes:

+ ++++ + + + + + + + + + + + +

new_node([name, label, value, children, parent])

Create a new CGNS node

new_child(parent, name[, label, value, children])

Create a new CGNS node as a child of an other node

update_child(parent, name[, label, value, ...])

Create a child or update its attributes

+

The previous snippet can thus be rewritted in more compact form:

+
>>> pnode = PT.new_node("ParentNode", "UserDefinedData_t", 3.14)
+>>> node = PT.new_child(pnode, "MyNode", "UserDefinedData_t", [1,2,3])
+>>> PT.update_node(node, name="MyUpdatedNode")
+>>> PT.print_tree(pnode)
+ParentNode UserDefinedData_t R8 [3.14]
+└───MyUpdatedNode UserDefinedData_t I4 [1 2 3]
+
+
+
+

See also

+

In practice, it is common to use presets for a quicker +and CGNS/SIDS-compliant creation of nodes with a specific label.

+
+
+
+

API reference

+
+
+get_name(node)
+

Return the name of a CGNSNode

+
+
Parameters
+

node (CGNSTree) – Input node

+
+
Returns
+

str – Name of the node

+
+
+

Example

+
>>> PT.get_name(PT.new_node(name='MyNodeName', label='Zone_t'))
+'MyNodeName'
+
+
+
+ +
+
+get_label(node)
+

Return the label of a CGNSNode

+
+
Parameters
+

node (CGNSTree) – Input node

+
+
Returns
+

str – Label of the node

+
+
+

Example

+
>>> PT.get_label(PT.new_node('Zone', label='Zone_t'))
+'Zone_t'
+
+
+
+ +
+
+get_value(node, raw=False)
+

Return the value of a CGNSNode

+

If value is an array of characters, it returned as a (or a +sequence of) string, unless raw parameter is True.

+
+
Parameters
+
    +
  • node (CGNSTree) – Input node

  • +
  • raw (bool) – If True, always return the numpy array

  • +
+
+
Returns
+

CGNSValue – Value of the node

+
+
+

Example

+
>>> PT.get_value(PT.new_node('MyNode', value=3.14))
+array([3.14], dtype=float32)
+
+
+
+ +
+
+get_value_type(node)
+

Return the value type of a CGNSNode

+

This two letters string identifies the datatype of the node and belongs to +["MT", "B1", "C1", "I4", "U4", "I8", "U8", "R4", "R8", "X4", "X8"]

+
+
Parameters
+

node (CGNSTree) – Input node

+
+
Returns
+

str – Value type of the node

+
+
+

Example

+
>>> PT.get_value_type(PT.new_node('MyNode', value=None))
+'MT'
+
+
+
+ +
+
+get_value_kind(node)
+

Return the value kind of a CGNSNode

+

If node is not empty, this one letter string identifies the +datakind of the node and belongs to +["MT", "B", "C", "I", "U", "R", "X"]

+
+
Parameters
+

node (CGNSTree) – Input node

+
+
Returns
+

str – Value kind of the node

+
+
+

Example

+
>>> PT.get_value_kind(PT.new_node('MyNode', value=3.14))
+'R'
+
+
+
+ +
+
+get_children(node)
+

Return the list of children of a CGNSNode

+
+
Parameters
+

node (CGNSTree) – Input node

+
+
Returns
+

List[CGNSTree] – Children of the node

+
+
+

Example

+
>>> len(PT.get_children(PT.new_node('MyNode')))
+0
+
+
+
+ +
+
+set_name(node, name)
+

Set the name of a CGNSNode

+
+
Parameters
+
    +
  • node (CGNSTree) – Input node

  • +
  • name (str) – Name to be set

  • +
+
+
Warns
+

RuntimeWarning – If name is longer than 32 characters

+
+
Raises
+

ValueError – If name is not valid

+
+
+

Example

+
>>> node = PT.new_node('Node')
+>>> PT.set_name(node, 'UpdatedNodeName')
+
+
+
+ +
+
+set_label(node, label)
+

Set the label of a CGNSNode

+
+
Parameters
+
    +
  • node (CGNSTree) – Input node

  • +
  • label (str) – Label to be set

  • +
+
+
Warns
+

RuntimeWarning – If label does not belong to SIDS label list

+
+
Raises
+

ValueError – If label is not valid

+
+
+

Example

+
>>> node = PT.new_node('BCNode')
+>>> PT.set_label(node, 'BC_t')
+
+
+
+ +
+
+set_value(node, value)
+

Set the value of a CGNSNode

+

If value is neither None nor a f-contiguous numpy array, +it is converted to a suitable numpy array depending on its type:

+
    +
  • Literal (or sequence of) floats are converted to R4 arrays

  • +
  • Literal (or sequence of) ints are converted to I4 arrays if possible, I8 otherwise

  • +
  • Numpy scalars keep their corresponding kind

  • +
  • Strings are converted to numpy bytearrays

  • +
  • Sequence of N strings are converted to numpy (32,N) shaped bytearrays

  • +
  • Nested sequence of strings (M sequences of N strings) are converted +to numpy (32, N, M) shaped bytearrays

  • +
+
+
Parameters
+
    +
  • node (CGNSTree) – Input node

  • +
  • value (Any) – Value to be set

  • +
+
+
+

Example

+
>>> node = PT.new_node('Node')
+>>> PT.set_value(node, [3,2,1])
+
+
+
+ +
+
+set_children(node, children)
+

Set the children list of a CGNSNode

+

This will replace the existing children with the provided list. +See also: add_child()

+
+
Parameters
+
    +
  • node (CGNSTree) – Input node

  • +
  • children (List[CGNSTree]) – Children to be set

  • +
+
+
+

Example

+
>>> node = PT.new_node('Zone', 'Zone_t')
+>>> PT.add_child(node, PT.new_node('ZoneType', 'ZoneType_t', 'Structured'))
+>>> PT.set_children(node, []) # Replace the children with the given list
+>>> len(PT.get_children(node))
+0
+
+
+
+ +
+
+add_child(node, child)
+

Append a child node to the children list of a CGNSNode

+
+
Parameters
+
    +
  • node (CGNSTree) – Input node

  • +
  • child (CGNSTree) – Child node to be add

  • +
+
+
Raises
+

RuntimeError – If a node with same name than child already exists

+
+
+

Example

+
>>> node = PT.new_node('Zone', 'Zone_t')
+>>> PT.add_child(node, PT.new_node('ZoneType', 'ZoneType_t', 'Structured'))
+
+
+
+ +
+
+update_node(node, name=UNSET, label=UNSET, value=UNSET, children=UNSET)
+

Update some attribute of a CGNSNode

+

Parameters which are provided to the function trigger the update of their +corresponding attribute.

+
+
Parameters
+
+
+
+

Example

+
>>> node = PT.new_node('Zone')
+>>> PT.update_node(node, label='Zone_t', value=[[11,10,0]])
+>>> node
+['Zone', array([[11,10,0]], dtype=int32), [], 'Zone_t']
+
+
+
+ +
+
+new_node(name='Node', label='UserDefined_t', value=None, children=[], parent=None)
+

Create a new CGNS node

+

If parent is not None, this node is appended as a child to the parent node.

+
+
Parameters
+
    +
  • name (str) – Name of the created node – see set_name()

  • +
  • label (str) – Label of the created node – see set_label()

  • +
  • value (Any) – Value of the created node – see set_value()

  • +
  • children (List[CGNSTree]) – Value of the created node – see set_children()

  • +
  • parent (CGNSTree or None) – Other node, where created node should be attached

  • +
+
+
Returns
+

CGNSTree – Created node

+
+
+

Example

+
>>> zone = PT.new_node('Zone', label='Zone_t') # Basic node creation
+>>> PT.new_node('ZoneType', 'ZoneType_t', "Unstructured",
+...             parent=zone) # Create and attach to a parent
+>>> PT.print_tree(zone)
+Zone Zone_t
+└───ZoneType ZoneType_t "Unstructured"
+
+
+
+ +
+
+new_child(parent, name, label='UserDefined_t', value=None, children=[])
+

Create a new CGNS node as a child of an other node

+

This is an alternative form of new_node(), with mandatory parent argument.

+
+
Parameters
+

all – See new_node()

+
+
Returns
+

CGNSTree – Created node

+
+
+

Example

+
>>> zone = PT.new_node('Zone', label='Zone_t') # Basic node creation
+>>> PT.new_child(zone, 'ZoneType', 'ZoneType_t', "Unstructured")
+>>> PT.print_tree(zone)
+Zone Zone_t
+└───ZoneType ZoneType_t "Unstructured"
+
+
+
+ +
+
+update_child(parent, name, label=UNSET, value=UNSET, children=UNSET)
+

Create a child or update its attributes

+

This is an alternative form of new_child(), but this function allow the parent node +to already have a child of the given name: in this case, its attributes are updated. +Otherwise, the child is created with the default values of new_child()

+
+
Parameters
+

all – See new_child()

+
+
Returns
+

CGNSTree – Created node

+
+
+

Example

+
>>> zone = PT.new_node('Zone', label='Zone_t') # Basic node creation
+>>> PT.update_child(zone, 'ZoneType', 'ZoneType_t') # Child is created
+>>> PT.update_child(zone, 'ZoneType', value="Unstructured") # Child is updated
+>>> PT.print_tree(zone)
+Zone Zone_t
+└───ZoneType ZoneType_t "Unstructured"
+
+
+
+ +
+
+print_tree(tree, out=sys.stdout, **kwargs)
+

Display the arborescence of a CGNSTree

+

By default, display will be done in the standard output. It is possible to print +in a file by setting out to either a string or a python file object:

+
PT.print_tree(tree)                # Print tree in stdout
+PT.print_tree(tree, 'tree.txt')    # Print tree in a tree.txt file
+with open('out.txt', 'w') as f:
+    PT.print_tree(tree, f)         # Print tree in f
+
+
+

In addition, the following kwargs can be used to parametrize the output:

+
    +
  • colors (bool) – If False, disable the colors in rendered text. Default to True +for stdout or stderr output, and False for file output.

  • +
  • verbose (bool) – If True, data arrays are displayed (using numpy display settings). +Otherwise, small arrays are printed, and large arrays only show their size. +Defaults to False.

  • +
  • max_depth (int) – Stop printing once max_depth is reached. By default, displays all nodes.

  • +
  • print_if (callable) – If set, it must be a function with the signature : f(n:CGNSTree) -> bool. +The branches not leading to a node that evaluates to True are removed from displayed tree.

  • +
+
+
Parameters
+
    +
  • tree (CGNSTree) – Node to be printed

  • +
  • out (TextIO) – Where to print

  • +
+
+
+
+

See also

+

To display a tree in custom output, use function to_string() +which takes the same parameters, except out, and return the string +representation of the tree.

+
+

Examples

+
>>> tree = PT.yaml.parse_yaml_cgns.to_node('''
+... Base CGNSBase_t [3,3]:
+...   Wall Family_t:
+...   MyZone Zone_t I4 [[16,6,0]]:
+...     GridCoordinates GridCoordinates_t:
+...       CoordinateX DataArray_t [0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3]:
+... ''')
+>>> PT.print_tree(tree)
+Base CGNSBase_t I4 [3 3]
+├───Wall Family_t
+└───MyZone Zone_t I4 [[16  6  0]]
+    └───GridCoordinates GridCoordinates_t
+        └───CoordinateX DataArray_t I4 (16,)
+>>> PT.print_tree(tree, max_depth=1)
+Base CGNSBase_t I4 [3 3]
+├───Wall Family_t
+└───MyZone Zone_t I4 [[16  6  0]]
+    ╵╴╴╴ (1 child masked)
+
+
+

On the next example, notice that the branches not leading to ‘CoordinateX’ are +not printed; and, because of verbose option, all the coordinate array is displayed.

+
>>> PT.print_tree(tree, verbose=True,
+...               print_if=lambda n : PT.get_name(n) == 'CoordinateX')
+Base CGNSBase_t I4 [3 3]
+└───MyZone Zone_t I4 [[16  6  0]]
+    └───GridCoordinates GridCoordinates_t
+        └───CoordinateX DataArray_t I4 (16,)
+            [0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3]
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/maia_pytree/inspect.html b/docs/1.3/maia_pytree/inspect.html new file mode 100644 index 00000000..f8f8dd30 --- /dev/null +++ b/docs/1.3/maia_pytree/inspect.html @@ -0,0 +1,1353 @@ + + + + + + + + + + Node inspection — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Node inspection

+

To avoid redoing several times the most commun operations, maia.pytree provide +several functions extracting usefull data from a specific kind of CGNSNode.

+

All these functions are CGNS/SIDS aware, meaning that they are garanteed to succeed +only if the input tree is respecting the standard.

+

They are also read only; i.e. input tree will not be modified by any +of these calls.

+
+

Overview

+

Here is a summary of the available functions, depending of the input node:

+

Tree These functions apply to CGNSTree_t node

+ ++++ + + + + + + + + +

find_connected_zones

Gather the zones of the tree into groups connected by 1to1 connectivities

find_periodic_jns

Gather the periodic joins of the tree according to their periodicity values

+

Zone These functions apply to Zone_t node

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Type

Return the kind of a Zone_t node

IndexDimension

Return the IndexDimension of a Zone_t node

VertexSize

Return the number of vertices per direction of a Zone_t node

FaceSize

Return the number of faces per direction of a Zone_t node

CellSize

Return the number of cells per direction of a Zone_t node

VertexBoundarySize

Return the number of boundary vertices per direction of a Zone_t node

NGonNode

Return the Element_t node of kind NGON_n of a Zone_t node

NFaceNode

Return the Element_t node of kind NFACE_n of a Zone_t node

CellDimension

Return the CellDimension of a Zone_t node

n_cell

Return the total number of cells of a Zone_t node

n_face

Return the total number of faces of a Zone_t node

n_vtx

Return the total number of vertices of a Zone_t node

n_vtx_bnd

Return the total number of boundary vertices of a Zone_t node

has_ngon_elements

Return True if some Element_t node of kind NGON_n exists in the Zone_t node

has_nface_elements

Return True if some Element_t node of kind NFACE_n exists in the Zone_t node

coordinates

Return the coordinate arrays of the Zone_t node

get_ordered_elements

Return the Elements under a Zone_t node, sorted according to their ElementRange

get_ordered_elements_per_dim

Return the Elements under a Zone_t node, gathered according to their dimension

get_elt_range_per_dim

Return the min & max element number of each dimension found in a Zone_t node

elt_ordering_by_dim

Return a flag indicating if elements belonging to a Zone_t node are sorted

+

Element These functions apply to Element_t node

+ ++++ + + + + + + + + + + + + + + + + + + + + +

CGNSName

Return the generic name of an Element_t node

Dimension

Return the dimension of an Element_t node

NVtx

Return the number of vertices of an Element_t node

Range

Return the value of the ElementRange of an Element_t node

Size

Return the size (number of elements) of an Element_t node

Type

Return the type of an Element_t node

+

GridConnectivity These functions apply to GridConnectivity_t and GridConnectivity1to1_t nodes

+ ++++ + + + + + + + + + + + + + + + + + + + + +

Type

Return the type of a GridConnectivity node

Transform

Return the Transform specification of a GridConnectivity1to1 node.

is1to1

Return True if the GridConnectivity node is of type 'Abutting1to1'

isperiodic

Return True if the GridConnectivity node is periodic

ZoneDonorPath

Return the path of the opposite zone of a GridConnectivity node

periodic_values

Return the periodic transformation of a GridConnectivity node

+

Subset These functions apply to nodes having a PointList or a PointRange

+ ++++ + + + + + + + + + + + + + + + + + +

getPatch

Return the PointList or PointRange node defining the Subset node

GridLocation

Return the GridLocation value of a Subset node

ZSRExtent

Return the path of the node to which the ZoneSubRegion node maps

n_elem

Return the number of mesh elements included in a Subset node

normal_axis

Return the normal direction of a structured subset.

+
+

Note

+

Functions are displayed below as static methods, gathered into classes. +This is an implementation detail to put functions into namespaces : they should +be used as usual, with their name prefixed by the label name:

+
>>> PT.Zone.Type(zone_node) # Apply on a Zone_t node
+>>> PT.GridConnectivity.Type(gc_node) #Apply on a GC_t or GC1to1_t node
+
+
+
+
+
+

Methods detail

+
+
+class Tree
+

The following functions apply to the top level CGNSTree_t

+
+
+static find_connected_zones(tree)
+

Gather the zones of the tree into groups connected by 1to1 connectivities

+

Only non periodic joins are considered. Output paths start at tree level +(Basename/Zonename).

+
+
Parameters
+

tree (CGNSTree) – Input CGNSTree_t node

+
+
Returns
+

List[List[str]] – grouped paths of zones

+
+
+

Example

+
>>> tree = PT.yaml.parse_yaml_cgns.to_cgns_tree('''
+... Base CGNSBase_t:
+...   Zone1 Zone_t:
+...     ZoneGridConnectivity ZoneGridConnectivity_t:
+...       match GridConnectivity1to1_t "Zone3":
+...   Zone2 Zone_t:
+...   Zone3 Zone_t:
+...     ZoneGridConnectivity ZoneGridConnectivity_t:
+...       match GridConnectivity1to1_t "Zone1":
+... ''')
+>>> PT.Tree.find_connected_zones(tree)
+[['Base/Zone2'], ['Base/Zone1', 'Base/Zone3']]
+
+
+
+ +
+
+static find_periodic_jns(tree, rtol=1e-05, atol=0.0)
+

Gather the periodic joins of the tree according to their periodicity values

+

Returned paths starts at tree level.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input CGNSTree_t node

  • +
  • rtol (float, optional) – relative tolerance for periodicity comparaison

  • +
  • atol (float, optional) – absolute tolerance for periodicity comparaison

  • +
+
+
Returns
+

Pair of lists – at each indice,

+
    +
  • first list contains the periodic arrays for the group of joins

  • +
  • second list contains the paths of joins related to this value

  • +
+

+
+
+
+ +
+ +
+
+class Zone
+

The following functions apply to any Zone_t node

+
+
+static CellDimension(zone_node)
+

Return the CellDimension of a Zone_t node

+

CellDimension is the dimensionality of the cell in the mesh, and should be equal +to the first element of related CGNSBase_t value.

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int – CellDimension (1,2 or 3)

+
+
Raises
+

ValueError – if zone has no elements

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured')
+>>> PT.new_Elements('PYRA', 'PYRA_5', erange=[1,10],  parent=zone)
+>>> PT.Zone.CellDimension(zone)
+2
+
+
+
+ +
+
+static CellSize(zone_node)
+

Return the number of cells per direction of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int or ndarray of int – number of cells in each direction +for structured zones, total number of cells for unstructured zones

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured', size=[[11,10,0]])
+>>> PT.Zone.CellSize(zone)
+10
+
+
+
+ +
+
+static FaceSize(zone_node)
+

Return the number of faces per direction of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int or list of int – number of faces in each direction +for structured zones, total number of faces for unstructured zones

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Structured', size=[[11,10,0], [6,5,0]])
+>>> PT.Zone.FaceSize(zone)
+[55, 60]
+
+
+
+

Warning

+

Unstructured zones are supported only if they have a NGon connectivity

+
+
+ +
+
+static IndexDimension(zone_node)
+

Return the IndexDimension of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int – IndexDimension (1,2 or 3)

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured', size=[[11,10,0]])
+>>> PT.Zone.IndexDimension(zone)
+1
+
+
+
+ +
+
+static NFaceNode(zone_node)
+

Return the Element_t node of kind NFACE_n of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

CGNSTree – NFace node

+
+
Raises
+

RuntimeError – if not exactly one NFACE_n element node exists in zone

+
+
+
+ +
+
+static NGonNode(zone_node)
+

Return the Element_t node of kind NGON_n of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

CGNSTree – NGon node

+
+
Raises
+

RuntimeError – if not exactly one NGON_n element node exists in zone

+
+
+
+ +
+
+static Type(zone_node)
+

Return the kind of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

str – One of ‘Structured’, ‘Unstructured’, ‘UserDefined’ or ‘Null’

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured')
+>>> PT.Zone.Type(zone)
+'Unstructured'
+
+
+
+ +
+
+static VertexBoundarySize(zone_node)
+

Return the number of boundary vertices per direction of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int or ndarray of int – number of boundary vtx in each direction +for structured zones, total number of boundary vtx for unstructured zones

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Structured', size=[[11,10,0],[6,5,0]])
+>>> PT.Zone.VertexBoundarySize(zone)
+array([0, 0], dtype=int32)
+
+
+
+ +
+
+static VertexSize(zone_node)
+

Return the number of vertices per direction of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int or ndarray of int – number of vertices in each direction +for structured zones, total number of vertices for unstructured zones

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Structured', size=[[11,10,0], [6,5,0], [2,1,0]])
+>>> PT.Zone.VertexSize(zone)
+array([11, 6, 2], dtype=int32)
+
+
+
+ +
+
+static coordinates(zone_node, name=None)
+

Return the coordinate arrays of the Zone_t node

+

Only cartesian coordinates are supported.

+
+
Parameters
+
    +
  • zone_node (CGNSTree) – Input Zone_t node

  • +
  • name (str, optional) – Name of the GridCoordinates node from which coordinates are taken. +If not specified, first container found is used.

  • +
+
+
Returns
+

Triplet of ndarray or None – for each direction, corresponding coordinate array or None +if physicalDimension is != 3

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured')
+>>> PT.new_GridCoordinates(fields={'CoordinateX' : [0., 0.5, 1.],
+...                                'CoordinateY' : [.5, .5, .5]},
+...                        parent=zone)
+>>> PT.Zone.coordinates(zone)
+(array([0.,0.5,1.], dtype=float32), array([0.5,0.5,0.5], dtype=float32), None)
+
+
+
+ +
+
+static elt_ordering_by_dim(zone_node)
+

Return a flag indicating if elements belonging to a Zone_t node are sorted

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int – Flag indicating how elements are sorted:

+
    +
  • 1 if elements of lower dimension have lower ElementRange

  • +
  • - 1 if elements of lower dimension have higher ElementRange

  • +
  • 0 if elements are not sorted

  • +
+

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured')
+>>> PT.new_Elements('TETRA', 'TETRA_4', erange=[1,10], parent=zone)
+>>> PT.new_Elements('TRI', 'TRI_3', erange=[11,30], parent=zone)
+>>> PT.Zone.elt_ordering_by_dim(zone)
+-1
+
+
+
+ +
+
+static get_elt_range_per_dim(zone_node)
+

Return the min & max element number of each dimension found in a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

list of 4 pairs – min and max element id for each dimension

+
+
Raises
+

RuntimeError – if elements of different dimension are interlaced

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured')
+>>> PT.new_Elements('PYRA', 'PYRA_5', erange=[1,10],  parent=zone)
+>>> PT.new_Elements('TRI',  'TRI_3',  erange=[11,30], parent=zone)
+>>> PT.new_Elements('BAR',  'BAR_2',  erange=[31,40], parent=zone)
+>>> PT.Zone.get_elt_range_per_dim(zone)
+[[0, 0], [31, 40], [11, 30], [1, 10]]
+
+
+
+ +
+
+static get_ordered_elements(zone_node)
+

Return the Elements under a Zone_t node, sorted according to their ElementRange

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

list of CGNSTree – Elements_t nodes

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured')
+>>> PT.new_Elements('TETRA', 'TETRA_4', erange=[1,10], parent=zone)
+>>> PT.new_Elements('TRI', 'TRI_3', erange=[11,30], parent=zone)
+>>> [PT.get_name(node) for node in PT.Zone.get_ordered_elements(zone)]
+['TETRA', 'TRI']
+
+
+
+ +
+
+static get_ordered_elements_per_dim(zone_node)
+

Return the Elements under a Zone_t node, gathered according to their dimension

+

Within each dimension, Elements are sorted according to their ElementRange

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

list of 4 list of CGNSTree – Elements_t nodes

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured')
+>>> PT.new_Elements('PYRA', 'PYRA_5', erange=[1,10],  parent=zone)
+>>> PT.new_Elements('TRI',  'TRI_3',  erange=[11,30], parent=zone)
+>>> PT.new_Elements('QUAD', 'QUAD_4', erange=[31,40], parent=zone)
+>>> [len(elts) for elts in PT.Zone.get_ordered_elements_per_dim(zone)]
+[0, 0, 2, 1]
+
+
+
+ +
+
+static has_nface_elements(zone_node)
+

Return True if some Element_t node of kind NFACE_n exists in the Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

bool

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Structured', size=[[11,10,0], [6,5,0]])
+>>> PT.Zone.has_nface_elements(zone)
+False
+
+
+
+ +
+
+static has_ngon_elements(zone_node)
+

Return True if some Element_t node of kind NGON_n exists in the Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

bool

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Structured', size=[[11,10,0], [6,5,0]])
+>>> PT.new_NGonElements(parent=zone)
+>>> PT.Zone.has_ngon_elements(zone)
+True
+
+
+
+ +
+
+static n_cell(zone_node)
+

Return the total number of cells of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int – number of cells

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Structured', size=[[11,10,0], [6,5,0]])
+>>> PT.Zone.n_cell(zone)
+50
+
+
+
+ +
+
+static n_face(zone_node)
+

Return the total number of faces of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int – number of faces

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Structured', size=[[11,10,0], [6,5,0]])
+>>> PT.Zone.n_face(zone)
+115
+
+
+
+

Warning

+

Unstructured zones are supported only if they have a NGon connectivity

+
+
+ +
+
+static n_vtx(zone_node)
+

Return the total number of vertices of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int – number of vertices

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured', size=[[11,10,0]])
+>>> PT.Zone.n_vtx(zone)
+11
+
+
+
+ +
+
+static n_vtx_bnd(zone_node)
+

Return the total number of boundary vertices of a Zone_t node

+
+
Parameters
+

zone_node (CGNSTree) – Input Zone_t node

+
+
Returns
+

int – number of boundary vertices

+
+
+

Example

+
>>> zone = PT.new_Zone(type='Unstructured', size=[[11,10,0]])
+>>> PT.Zone.n_vtx_bnd(zone)
+0
+
+
+
+ +
+ +
+
+class Element
+

The following functions apply to any Element_t node

+
+
+static CGNSName(elt_node)
+

Return the generic name of an Element_t node

+
+
Parameters
+

elt_node (CGNSTree) – Input Element_t node

+
+
Returns
+

str – CGNS name corresponding to this element kind

+
+
+

Example

+
>>> elt = PT.new_NFaceElements('MyElements')
+>>> PT.Element.CGNSName(elt)
+'NFACE_n'
+
+
+
+ +
+
+static Dimension(elt_node)
+

Return the dimension of an Element_t node

+
+
Parameters
+

elt_node (CGNSTree) – Input Element_t node

+
+
Returns
+

int – Dimension of this element kind (0, 1, 2 or 3)

+
+
+

Example

+
>>> elt = PT.new_Elements(type='TRI_3')
+>>> PT.Element.Dimension(elt)
+2
+
+
+
+ +
+
+static NVtx(elt_node)
+

Return the number of vertices of an Element_t node

+
+
Parameters
+

elt_node (CGNSTree) – Input Element_t node

+
+
Returns
+

int – Number of vertices for this element kind

+
+
+

Example

+
>>> elt = PT.new_Elements(type='PYRA_5')
+>>> PT.Element.NVtx(elt)
+5
+
+
+
+ +
+
+static Range(elt_node)
+

Return the value of the ElementRange of an Element_t node

+
+
Parameters
+

elt_node (CGNSTree) – Input Element_t node

+
+
Returns
+

ndarray – ElementRange of the node

+
+
+

Example

+
>>> elt = PT.new_Elements(type='PYRA_5', erange=[21,40])
+>>> PT.Element.Range(elt)
+array([21, 40], dtype=int32)
+
+
+
+ +
+
+static Size(elt_node)
+

Return the size (number of elements) of an Element_t node

+
+
Parameters
+

elt_node (CGNSTree) – Input Element_t node

+
+
Returns
+

int – Number of elements described by the node

+
+
+

Example

+
>>> elt = PT.new_Elements(type='PYRA_5', erange=[21,40])
+>>> PT.Element.Size(elt)
+20
+
+
+
+ +
+
+static Type(elt_node)
+

Return the type of an Element_t node

+
+
Parameters
+

elt_node (CGNSTree) – Input Element_t node

+
+
Returns
+

int – CGNS code corresponding to this element kind

+
+
+

Example

+
>>> elt = PT.new_Elements(type='TETRA_4')
+>>> PT.Element.Type(elt)
+10
+
+
+
+ +
+ +
+
+class GridConnectivity
+
+
+static Transform(gc_node, as_matrix=False)
+

Return the Transform specification of a GridConnectivity1to1 node.

+
+
Parameters
+
    +
  • gc_node (CGNSTree) – Input GridConnectivity1to1 node

  • +
  • as_matrix (bool) – If True, return the transform matrix. Otherwise, return +the transform vector.

  • +
+
+
Returns
+

ndarray – Transform specification

+
+
+

Example

+
>>> gc = PT.new_GridConnectivity1to1(transform=[-2,3,1])
+>>> PT.GridConnectivity.Transform(gc, True)
+array([[ 0,  0,  1],
+       [-1,  0,  0],
+       [ 0,  1,  0]])
+
+
+
+ +
+
+static Type(gc_node)
+

Return the type of a GridConnectivity node

+
+
Parameters
+

gc_node (CGNSTree) – Input GridConnectivity node

+
+
Returns
+

str – One of ‘Null’, ‘UserDefined’, ‘Overset’, ‘Abutting’ or ‘Abutting1to1’

+
+
+

Example

+
>>> gc = PT.new_GridConnectivity1to1('GC')
+>>> PT.GridConnectivity.Type(gc)
+'Abutting1to1'
+
+
+
+ +
+
+static ZoneDonorPath(gc_node, cur_base_name)
+

Return the path of the opposite zone of a GridConnectivity node

+
+
Parameters
+
    +
  • gc_node (CGNSTree) – Input GridConnectivity node

  • +
  • cur_base_name (str) – Name of the parent base of this node

  • +
+
+
Returns
+

str – Path (Basename/Zonename) of the opposite zone

+
+
+

Example

+
>>> gc = PT.new_GridConnectivity('GC', donor_name='OtherBase/OppZone')
+>>> PT.GridConnectivity.ZoneDonorPath(gc, 'Base')
+'OtherBase/OppZone'
+
+
+
+ +
+
+static is1to1(gc_node)
+

Return True if the GridConnectivity node is of type ‘Abutting1to1’

+
+
Parameters
+

gc_node (CGNSTree) – Input GridConnectivity node

+
+
Returns
+

bool

+
+
+

Example

+
>>> gc = PT.new_GridConnectivity('GC', type='Overset')
+>>> PT.GridConnectivity.is1to1(gc)
+False
+
+
+
+ +
+
+static isperiodic(gc_node)
+

Return True if the GridConnectivity node is periodic

+
+
Parameters
+

gc_node (CGNSTree) – Input GridConnectivity node

+
+
Returns
+

bool

+
+
+

Example

+
>>> gc = PT.new_GridConnectivity('GC', type='Overset')
+>>> PT.GridConnectivity.isperiodic(gc)
+False
+
+
+
+ +
+
+static periodic_values(gc_node)
+

Return the periodic transformation of a GridConnectivity node

+
+
Parameters
+

gc_node (CGNSTree) – Input GridConnectivity node

+
+
Returns
+

Triplet of ndarray or None – values of RotationCenter, RotationAngle and Translation, stored +in a named tuple

+
+
+

Example

+
>>> gc = PT.new_GridConnectivity('GC')
+>>> PT.new_GridConnectivityProperty({'translation' : [1., 0, 0]}, parent=gc)
+>>> PT.GridConnectivity.periodic_values(gc)
+PeriodicValues(RotationCenter=array([0., 0., 0.], dtype=float32),
+               RotationAngle=array([0., 0., 0.], dtype=float32),
+               Translation=array([1., 0., 0.], dtype=float32))
+
+
+
+ +
+ +
+
+class Subset
+

A subset is a node having a PointList or a PointRange

+
+
+static GridLocation(subset_node)
+

Return the GridLocation value of a Subset node

+
+
Parameters
+

subset_node (CGNSTree) – Input Subset node

+
+
Returns
+

str – One of ‘Null’, ‘UserDefined’, ‘Vertex’, ‘CellCenter’, ‘FaceCenter’, +‘IFaceCenter’, ‘JFaceCenter’, ‘KFaceCenter’, or ‘EdgeCenter’

+
+
+

Example

+
>>> bc = PT.new_BC('BC', loc='FaceCenter')
+>>> PT.Subset.GridLocation(bc)
+'FaceCenter'
+
+
+
+ +
+
+static ZSRExtent(zsr_node, zone_node)
+

Return the path of the node to which the ZoneSubRegion node maps

+

Path start from zone_node and can point to a BC, a GC or the ZSR itself. +This function only make sense for ZoneSubRegion_t nodes.

+
+
Parameters
+
    +
  • zsr_node (CGNSTree) – Input ZoneSubRegion_t node

  • +
  • zone_node (CGNSTree) – Parent Zone_t node

  • +
+
+
Returns
+

str – path of zone defining the ZSR extent

+
+
+

Example

+
>>> zone = PT.new_Zone('Zone')
+>>> bc   = PT.new_BC('RelevantBC', parent=PT.new_ZoneBC(parent=zone))
+>>> zsr  = PT.new_ZoneSubRegion('ZSR', bc_name='RelevantBC')
+>>> PT.Subset.ZSRExtent(zsr, zone)
+'ZoneBC/RelevantBC'
+
+
+
+ +
+
+static getPatch(subset_node)
+

Return the PointList or PointRange node defining the Subset node

+
+
Parameters
+

subset_node (CGNSTree) – Input Subset node

+
+
Returns
+

CGNSTree – PointList or PointRange node

+
+
+

Example

+
>>> bc = PT.new_BC('BC', loc='FaceCenter', point_list=[[1,2,3,4]])
+>>> PT.Subset.getPatch(bc)
+['PointList', array([[1, 2, 3, 4]], dtype=int32), [], 'IndexArray_t']
+
+
+
+ +
+
+static n_elem(subset_node)
+

Return the number of mesh elements included in a Subset node

+
+
Parameters
+

subset_node (CGNSTree) – Input Subset node

+
+
Returns
+

int – Number of elements

+
+
+

Example

+
>>> gc = PT.new_GridConnectivity1to1('GC', point_range=[[10,1],[1,10]])
+>>> PT.Subset.n_elem(gc)
+100
+>>> bc = PT.new_BC('BC', loc='FaceCenter', point_list=[[1,2,3,4]])
+>>> PT.Subset.n_elem(bc)
+4
+
+
+
+ +
+
+static normal_axis(subset_node)
+

Return the normal direction of a structured subset.

+

This function is only relevant for subsets defining a 2d structured region +(having a PointRange node).

+
+
Parameters
+

subset_node (CGNSTree) – Input Subset node

+
+
Returns
+

int – Normal axis of the subset (0,1 or 2)

+
+
Raises
+

ValueError – if normal axis can not be determined

+
+
+

Example

+
>>> bc = PT.new_BC(point_range=[[1,10], [5,5], [1,100]], loc='Vertex')
+>>> PT.Subset.normal_axis(bc)
+1
+
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/maia_pytree/presets.html b/docs/1.3/maia_pytree/presets.html new file mode 100644 index 00000000..4b5f2c26 --- /dev/null +++ b/docs/1.3/maia_pytree/presets.html @@ -0,0 +1,1030 @@ + + + + + + + + + + Node creation presets — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Node creation presets

+

Although any tree could be creating using exclusivelly new_node() function, +maia.pytree provide some shortcuts to create nodes with relevant information.

+

In addition of reducing the amount of code to write, it also:

+
    +
  • hides the node structure details : if you want to create an Unstructured zone, +just tell to new_Zone function: you don’t need to know that this information +should be stored in a ZoneType node of label ZoneType_t node under the zone;

  • +
  • performs checks to prevent you to create non CGNS/SIDS-compliant nodes.

  • +
+
+

Generalities

+

Containers fields

+

When creating a Container node (ie. a node storing some fields, +such as a FlowSolution_t), a list of DataArray to create can be +provided through the fields parameter, which must be a dictionnary +mapping array names to array values: for example,

+
>>> fields = {'Density' : np.array([1, 1, 1.05], float),
+...           'Temperature' : [293., 293., 296.]}
+
+
+

when passed to new_FlowSolution(), +will created the requested fields:

+
>>> fs = PT.new_FlowSolution('FS', fields=fields)
+>>> PT.print_tree(fs)
+FS FlowSolution_t
+├───Density DataArray_t R8 [1.   1.   1.05]
+└───Temperature DataArray_t R4 [293. 293. 296.]
+
+
+

Notice that values which are not numpy array instance are converted following +set_value() rules.

+

Parent label check

+

All the new_...() functions (appart from new_CGNSTree()) take an optionnal +parent argument, which can be used to add the created node +to the parent children list. In this case, a warning will be issued if the hierarchic +relation is not CGNS/SIDS-compliant.

+
>>> zone = PT.new_Zone()
+>>> bc = PT.new_BC(parent=zone)
+RuntimeWarning: Attaching node BC (BC_t) under a Zone_t parent
+is not SIDS compliant. Admissible parent labels are ['ZoneBC_t'].
+
+
+

Return value

+
+

Important

+

All the functions listed in this page return a single value, +which is the created CGNSTree. For more readability, we omit the return section +in the API description.

+
+
+
+

Overview

+

Functions creating top level structures

+ ++++ + + + + + + + + + + + + + + +

new_CGNSTree

Create a CGNSTree_t node

new_CGNSBase

Create a CGNSBase_t node

new_BaseIterativeData

Create a BaseIterativeData_t node

new_Zone

Create a Zone_t node

+

Functions creating Family related nodes

+ ++++ + + + + + + + + +

new_Family

Create a Family_t node

new_FamilyName

Create a FamilyName_t or an AdditionalFamilyName_t node

+

Functions creating Zone related nodes

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

new_GridCoordinates

Create a GridCoordinates_t node

new_Elements

Create an Element_t node

new_NFaceElements

Create an Element_t node describing a NFACE_n connectivity

new_NGonElements

Create an Element_t node describing a NGON_n connectivity

new_ZoneBC

Create a ZoneBC_t node

new_ZoneGridConnectivity

Create a ZoneGridConnectivity_t node

new_FlowSolution

Create a FlowSolution_t node

new_ZoneSubRegion

Create a ZoneSubRegion_t node

new_BC

Create a BC_t node

new_GridConnectivity

Create a GridConnectivity_t node

new_GridConnectivity1to1

Create a GridConnectivity1to1_t node

new_GridConnectivityProperty

Create a GridConnectivityProperty node

+

Functions creating common sub nodes

+ ++++ + + + + + + + + + + + + + + +

new_GridLocation

Create a GridLocation_t node

new_DataArray

Create a DataArray_t node

new_IndexArray

Create an IndexArray_t node

new_IndexRange

Create an IndexRange_t node

+
+
+

API reference

+
+
+new_BC(name='BC', type='Null', *, point_range=None, point_list=None, loc=None, family=None, parent=None)
+

Create a BC_t node

+

The patch defining the BC must be provided using either point_range or +point_list parameter : both can no be used simultaneously.

+

Link to corresponding SIDS section: +BC_t

+
+
Parameters
+
    +
  • name (str) – Name of the created bc node

  • +
  • type (str) – Type of the boundary condition

  • +
  • point_range (ArrayLike) – PointRange array defining the BC

  • +
  • point_list (ArrayLike) – PointList array defining the BC

  • +
  • loc (str) – If specified, create a GridLocation taking this value

  • +
  • family (str) – If specified, create a FamilyName taking this value

  • +
  • parent (CGNSTree) – Node to which created bc should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_BC('BC', 'BCWall', point_list=[[1,5,10,15]],
+...                  loc='FaceCenter', family='WALL')
+>>> PT.print_tree(node)
+BC BC_t "BCWall"
+├───GridLocation GridLocation_t "FaceCenter"
+├───FamilyName FamilyName_t "WALL"
+└───PointList IndexArray_t I4 [[ 1  5 10 15]]
+
+
+
+ +
+
+new_BaseIterativeData(name='BaseIterativeData', *, time_values=None, iter_values=None, parent=None)
+

Create a BaseIterativeData_t node

+

Link to corresponding SIDS section: +BaseIterativeData_t

+
+
Parameters
+
    +
  • name (str) – Name of the created node

  • +
  • time_values (ArrayLike) – if provided, create a TimeValues child array

  • +
  • iter_values (ArrayLike) – if provided, create an IterationValues child array

  • +
  • parent (CGNSTree) – Node to which created node should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_BaseIterativeData(time_values=[0.0, 0.5, 1.0])
+>>> PT.print_tree(node)
+BaseIterativeData BaseIterativeData_t I4 [3]
+└───TimeValues DataArray_t R4 [0.  0.5 1. ]
+
+
+
+ +
+
+new_CGNSBase(name='Base', *, cell_dim=3, phy_dim=3, parent=None)
+

Create a CGNSBase_t node

+

Link to corresponding SIDS section: +CGNSBase_t

+
+
Parameters
+
    +
  • name (str) – Name of the created base

  • +
  • cell_dim (one of 1,2,3) – Cell dimension of the mesh

  • +
  • phy_dim (one of 1,2,3) – Physical dimension of the mesh

  • +
  • parent (CGNSTree) – Node to which created base should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_CGNSBase('Base', cell_dim=2)
+>>> PT.print_tree(node)
+Base CGNSBase_t I4 [2 3]
+
+
+
+ +
+
+new_CGNSTree(*, version=4.2)
+

Create a CGNSTree_t node

+
+
Parameters
+

version (float) – Number used to fill the CGNSLibraryVersion data

+
+
+

Example

+
>>> node = PT.new_CGNSTree()
+>>> PT.print_tree(node)
+CGNSTree CGNSTree_t
+└───CGNSLibraryVersion CGNSLibraryVersion_t R4 [4.2]
+
+
+
+ +
+
+new_DataArray(name, value, *, dtype=None, parent=None)
+

Create a DataArray_t node

+

The datatype of the DataArray can be enforced with the dtype parameter, which +must be a str value (eg I4, R8). If not provided, default conversion of +set_value() applies.

+

Link to corresponding SIDS section: +DataArray_t

+
+
Parameters
+
    +
  • name (str) – Name of the created data array node

  • +
  • value (ArrayLike) – value of the data array

  • +
  • dtype (DTypeLike) – If used, cast value to the specified type

  • +
  • parent (CGNSTree) – Node to which created data array should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_DataArray('Data', [1,2,3])
+>>> PT.print_tree(node)
+Data DataArray_t I4 [1 2 3]
+>>> node = PT.new_DataArray('Data', [1,2,3], dtype='R8')
+>>> PT.print_tree(node)
+Data DataArray_t R8 [1. 2. 3.]
+
+
+
+ +
+
+new_Elements(name='Elements', type='Null', *, erange=None, econn=None, parent=None)
+

Create an Element_t node

+

This function is designed to create standard elements. +See new_NGonElements() or new_NFaceElements() to create polygonal +elements.

+

Link to corresponding SIDS section: +Elements_t

+
+
Parameters
+
    +
  • name (str) – Name of the created element node

  • +
  • type (str) – CGNSName of the element section, for example PYRA_5

  • +
  • erange (ArrayLike) – ElementRange array of the elements

  • +
  • econn (ArrayLike) – ElementConnectivity array of the elements

  • +
  • parent (CGNSTree) – Node to which created elements should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_Elements('Edges', type='BAR_2', erange=[1,4],
+...                        econn=[1,2, 2,3, 3,4, 4,1])
+>>> PT.print_tree(node)
+Edges Elements_t I4 [3 0]
+├───ElementRange IndexRange_t I4 [1 4]
+└───ElementConnectivity DataArray_t I4 [1 2 2 3 3 4 4 1]
+
+
+
+ +
+
+new_Family(name='Family', *, family_bc=None, parent=None)
+

Create a Family_t node

+

Link to corresponding SIDS section: +Family_t

+
+
Parameters
+
    +
  • name (str) – Name of the created family

  • +
  • family_bc (str) – If specified, create a FamilyBC taking this value under the Family node

  • +
  • parent (CGNSTree) – Node to which created family should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_Family('WALL', family_bc='BCWall')
+>>> PT.print_tree(node)
+WALL Family_t
+└───FamilyBC FamilyBC_t "BCWall"
+
+
+
+ +
+
+new_FamilyName(family_name, as_additional='', parent=None)
+

Create a FamilyName_t or an AdditionalFamilyName_t node

+
+
Parameters
+
    +
  • family_name (str) – Name of the family to which the FamilyName node refers

  • +
  • as_additional (str) – If provided, node is created as an AdditionalFamilyName_t node +named after this str

  • +
  • parent (CGNSTree) – Node to which created node should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_FamilyName('MyFamily')
+>>> PT.print_tree(node)
+FamilyName FamilyName_t "MyFamily"
+>>> node = PT.new_FamilyName('MyFamily', as_additional='AddFamName')
+>>> PT.print_tree(node)
+AddFamName AdditionalFamilyName_t "MyFamily"
+
+
+
+ +
+
+new_FlowSolution(name='FlowSolution', *, loc=None, fields={}, parent=None)
+

Create a FlowSolution_t node

+

Link to corresponding SIDS section: +FlowSolution_t

+
+
Parameters
+
    +
  • name (str) – Name of the created flow solution node

  • +
  • loc (str) – If specified, create a GridLocation taking this value

  • +
  • fields (dict) – fields to create under the container (see fields setting)

  • +
  • parent (CGNSTree) – Node to which created flow solution should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_FlowSolution('FS', loc='CellCenter',
+...                            fields={'Density' : np.ones(125)})
+>>> PT.print_tree(node)
+FS FlowSolution_t
+├───GridLocation GridLocation_t "CellCenter"
+└───Density DataArray_t R8 (125,)
+
+
+
+ +
+
+new_GridConnectivity(name='GC', donor_name=None, type='Null', *, loc=None, point_range=None, point_range_donor=None, point_list=None, point_list_donor=None, parent=None)
+

Create a GridConnectivity_t node

+

The patch defining the GC must be provided using either point_range or +point_list parameter : both can no be used simultaneously. +The same applies for the opposite patch definition (using point_range_donor +or point_list_donor).

+

Link to corresponding SIDS section: +GridConnectivity_t

+
+
Parameters
+
    +
  • name (str) – Name of the created gc node

  • +
  • donor_name (str) – Name or path of the opposite zone

  • +
  • type (one of 'Null', 'UserDefined', 'Overset', 'Abutting' or 'Abutting1to1') – Type of the gc node

  • +
  • loc (str) – If specified, create a GridLocation taking this value

  • +
  • point_range (ArrayLike) – PointRange array defining the current patch

  • +
  • point_range_donor (ArrayLike) – PointRange array defining the opposite patch

  • +
  • point_list (ArrayLike) – PointList array defining the current patch

  • +
  • point_list_donor (ArrayLike) – PointList array defining the opposite patch

  • +
  • parent (CGNSTree) – Node to which created gc should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_GridConnectivity('GC', 'Zone', 'Abutting1to1',
+...           point_list=[[1,4,7]], point_list_donor=[[3,6,9]])
+>>> PT.print_tree(node)
+GC GridConnectivity_t "Zone"
+├───GridConnectivityType GridConnectivityType_t "Abutting1to1"
+├───PointList IndexArray_t I4 [[1 4 7]]
+└───PointListDonor IndexArray_t I4 [[3 6 9]]
+
+
+
+ +
+
+new_GridConnectivity1to1(name='GC', donor_name=None, *, point_range=None, point_range_donor=None, transform=None, parent=None)
+

Create a GridConnectivity1to1_t node

+

GridConnectivity1to1_t are reserved for structured zones. See +new_GridConnectivity() to create general GridConnectivity_t nodes.

+

Link to corresponding SIDS section: +GridConnectivity1to1_t

+
+
Parameters
+
    +
  • name (str) – Name of the created gc node

  • +
  • donor_name (str) – Name or path of the opposite zone

  • +
  • point_range (ArrayLike) – PointRange array defining the current patch

  • +
  • point_range_donor (ArrayLike) – PointRange array defining the opposite patch

  • +
  • transform (array of int) – short notation of the transformation matrix

  • +
  • parent (CGNSTree) – Node to which created gc should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_GridConnectivity1to1('GC', 'Zone', transform=[1,2,3],
+...     point_range=[[1,1],[1,10]], point_range_donor=[[5,5],[10,10]])
+>>> PT.print_tree(node)
+GC GridConnectivity1to1_t "Zone"
+├───Transform "int[IndexDimension]" I4 [1 2 3]
+├───PointRange IndexRange_t I4 [[ 1  1] [ 1 10]]
+└───PointRangeDonor IndexRange_t I4 [[ 5  5] [10 10]]
+
+
+
+ +
+
+new_GridConnectivityProperty(periodic={}, parent=None)
+

Create a GridConnectivityProperty node

+

The main interest of this function is to add periodic information to a GC_t node; +this can be done with the periodic parameter which maps the keys +‘rotation_angle’, ‘rotation_center’ and ‘translation’ to the corresponding arrays.

+

Missing keys defaults to np.zeros(3), users should be careful if +when the physical dimension of the mesh if lower than 3.

+

Link to corresponding SIDS section: +GridConnectivityProperty_t

+
+
Parameters
+
    +
  • periodic (dict) – Name of the created gc node

  • +
  • parent (CGNSTree) – Node to which created gc prop should be attached

  • +
+
+
+

Example

+
>>> perio = {"translation" : [1.0, 0.0, 0.0]}
+>>> node = PT.new_GridConnectivityProperty(perio)
+>>> PT.print_tree(node)
+GridConnectivityProperty GridConnectivityProperty_t
+└───Periodic Periodic_t
+    ├───RotationAngle DataArray_t R4 [0. 0. 0.]
+    ├───RotationCenter DataArray_t R4 [0. 0. 0.]
+    └───Translation DataArray_t R4 [1. 0. 0.]
+
+
+
+ +
+
+new_GridCoordinates(name='GridCoordinates', *, fields={}, parent=None)
+

Create a GridCoordinates_t node

+

Link to corresponding SIDS section: +GridCoordinates_t

+
+
Parameters
+
    +
  • name (str) – Name of the created gc node

  • +
  • fields (dict) – fields to create under the container (see fields setting)

  • +
  • parent (CGNSTree) – Node to which created gc should be attached

  • +
+
+
+

Example

+
>>> coords={'CoordinateX' : [1.,2.,3.], 'CoordinateY' : [1.,1.,1.]}
+>>> node = PT.new_GridCoordinates(fields=coords)
+>>> PT.print_tree(node)
+GridCoordinates GridCoordinates_t
+├───CoordinateX DataArray_t R4 [1. 2. 3.]
+└───CoordinateY DataArray_t R4 [1. 1. 1.]
+
+
+
+ +
+
+new_GridLocation(loc, parent=None)
+

Create a GridLocation_t node

+

Link to corresponding SIDS section: +GridLocation_t

+
+
Parameters
+
    +
  • loc (str) – Value to set in the grid location node

  • +
  • parent (CGNSTree) – Node to which created node should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_GridLocation('FaceCenter')
+>>> PT.print_tree(node)
+GridLocation GridLocation_t "FaceCenter"
+
+
+
+ +
+
+new_IndexArray(name='PointList', value=None, parent=None)
+

Create an IndexArray_t node

+

Note that the value array will not be reshaped and must consequently match the expected layout +(IndexDimension, N).

+

Link to corresponding SIDS section: +IndexArray_t

+
+
Parameters
+
    +
  • name (str) – Name of the created index array node

  • +
  • value (ArrayLike) – value of the index array

  • +
  • parent (CGNSTree) – Node to which created node should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_IndexArray(value=[[1,2,3]])
+>>> PT.print_tree(node)
+PointList IndexArray_t I4 [[1 2 3]]
+
+
+
+ +
+
+new_IndexRange(name='PointRange', value=None, parent=None)
+

Create an IndexRange_t node

+

Note that if needed, the value array will be reshaped to the expected layout +(IndexDimension, 2) (see example below).

+

Link to corresponding SIDS section: +IndexRange_t

+
+
Parameters
+
    +
  • name (str) – Name of the created index range node

  • +
  • value (ArrayLike) – value of the index range

  • +
  • parent (CGNSTree) – Node to which created node should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_IndexRange(value=[[1,10],[1,10]])
+>>> PT.print_tree(node)
+PointRange IndexRange_t I4 [[ 1 10] [ 1 10]]
+>>> node = PT.new_IndexRange(value=[1,10, 1,10, 1,1])
+>>> PT.print_tree(node)
+PointRange IndexRange_t I4 [[ 1 10] [ 1 10] [ 1  1]]
+
+
+
+ +
+
+new_NFaceElements(name='NFaceElements', *, erange=None, eso=None, ec=None, parent=None)
+

Create an Element_t node describing a NFACE_n connectivity

+

Link to corresponding SIDS section: +Elements_t

+
+
Parameters
+
    +
  • name (str) – Name of the created element node

  • +
  • erange (ArrayLike) – ElementRange array of the elements

  • +
  • eso (ArrayLike) – ElementStartOffset array of the elements

  • +
  • ec (ArrayLike) – ElementConnectivity array of the elements

  • +
  • parent (CGNSTree) – Node to which created elements should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_NFaceElements(erange=[5,5], eso=[0,4], ec=[1,2,3,4])
+>>> PT.print_tree(node)
+NFaceElements Elements_t I4 [23  0]
+├───ElementRange IndexRange_t I4 [5 5]
+├───ElementStartOffset DataArray_t I4 [0 4]
+└───ElementConnectivity DataArray_t I4 [1 2 3 4]
+
+
+
+ +
+
+new_NGonElements(name='NGonElements', *, erange=None, eso=None, ec=None, pe=None, pepos=None, parent=None)
+

Create an Element_t node describing a NGON_n connectivity

+

Link to corresponding SIDS section: +Elements_t

+
+
Parameters
+
    +
  • name (str) – Name of the created element node

  • +
  • erange (ArrayLike) – ElementRange array of the elements

  • +
  • eso (ArrayLike) – ElementStartOffset array of the elements

  • +
  • ec (ArrayLike) – ElementConnectivity array of the elements

  • +
  • pe (ArrayLike) – ParentElements array of the elements

  • +
  • pepos (ArrayLike) – ParentElementsPosition array of the elements

  • +
  • parent (CGNSTree) – Node to which created elements should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_NGonElements(erange=[1,4], eso=[0,3,6,9,12],
+...                            ec=[1,3,2, 1,2,4, 2,3,4, 3,1,4])
+>>> PT.print_tree(node)
+NGonElements Elements_t I4 [22  0]
+├───ElementRange IndexRange_t I4 [1 4]
+├───ElementStartOffset DataArray_t I4 [ 0  3  6  9 12]
+└───ElementConnectivity DataArray_t I4 (12,)
+
+
+
+ +
+
+new_Zone(name='Zone', *, type='Null', size=None, family=None, parent=None)
+

Create a Zone_t node

+

Note that the size array will not be reshaped and must consequently match the expected layout

+
+

[[n_vtx, n_cell, n_bnd_vtx] for each IndexDimension]

+
+

for example, [[11,10,0]] for an unstructured zone or [[11,10,0], [6,5,0]] for a 2D structured zone.

+

Link to corresponding SIDS section: +Zone_t

+
+
Parameters
+
    +
  • name (str) – Name of the created zone

  • +
  • type ({'Null', 'UserDefined', 'Structured' or 'Unstructured'}) – Type of the zone

  • +
  • size (ArrayLike) – Size of the zone.

  • +
  • family (str) – If specified, create a FamilyName refering to this family

  • +
  • parent (CGNSTree) – Node to which created Zone should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_Zone('Zone', type='Unstructured',
+...                    size=[[11,10,0]], family='Rotor')
+>>> PT.print_tree(node)
+Zone Zone_t I4 [[11 10  0]]
+├───ZoneType ZoneType_t "Unstructured"
+└───FamilyName FamilyName_t "Rotor"
+
+
+
+ +
+
+new_ZoneBC(parent=None)
+

Create a ZoneBC_t node

+
+
Parameters
+

parent (CGNSTree) – Node to which created ZBC should be attached

+
+
+
+ +
+
+new_ZoneGridConnectivity(name='ZoneGridConnectivity', parent=None)
+

Create a ZoneGridConnectivity_t node

+
+
Parameters
+
    +
  • name (str) – Name of the created ZoneGridConnectivity node

  • +
  • parent (CGNSTree) – Node to which created ZGC should be attached

  • +
+
+
+
+ +
+
+new_ZoneSubRegion(name='ZoneSubRegion', *, loc=None, point_range=None, point_list=None, bc_name=None, gc_name=None, family=None, fields={}, parent=None)
+

Create a ZoneSubRegion_t node

+

The patch defining the ZoneSubRegion must be provided using one of point_range, +point_list, bc_name or gc_name parameter : they can no be used simultaneously. +Setting a GridLocation with loc parameter makes sens only if a patch is explicitly defined +with point_range or point_list.

+

Link to corresponding SIDS section: +ZoneSubRegion_t

+
+
Parameters
+
    +
  • name (str) – Name of the created zsr node

  • +
  • loc (str) – If specified, create a GridLocation taking this value

  • +
  • point_range (ArrayLike) – PointRange array defining the ZSR extent

  • +
  • point_list (ArrayLike) – PointList array defining the ZSR extent

  • +
  • bc_name (str) – Name of the BC_t node defining the ZSR extent

  • +
  • gc_name (str) – Name of the GC_t node defining the ZSR extent

  • +
  • family (str) – If specified, create a FamilyName refering to this family

  • +
  • fields (dict) – fields to create under the container (see fields setting)

  • +
  • parent (CGNSTree) – Node to which created zsr should be attached

  • +
+
+
+

Example

+
>>> node = PT.new_ZoneSubRegion('Extraction', bc_name = 'Bottom',
+...                             fields={'Density' : np.ones(125)})
+>>> PT.print_tree(node)
+Extraction ZoneSubRegion_t
+├───BCRegionName Descriptor_t "Bottom"
+└───Density DataArray_t R8 (125,)
+>>> node = PT.new_ZoneSubRegion('Probe', loc='CellCenter',
+...                             point_list=[[104]])
+>>> PT.print_tree(node)
+Probe1 ZoneSubRegion_t
+├───GridLocation GridLocation_t "CellCenter"
+└───PointList IndexArray_t I4 [[104]]
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/maia_pytree/pytree_module.html b/docs/1.3/maia_pytree/pytree_module.html new file mode 100644 index 00000000..89a5d354 --- /dev/null +++ b/docs/1.3/maia_pytree/pytree_module.html @@ -0,0 +1,324 @@ + + + + + + + + + + The pytree module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

The pytree module

+

The maia.pytree module is dedicated to the exploration, manipulation and comparison +of python CGNS trees. This module is independant of maia representation of +parallel trees ; its content executes in a sequential context.

+

Motivations

+

This module tries to achieve the following goals:

+

Light, portable and easy to install

+

By avoiding compiled code and minimizing external dependencies, +maia.pytree should work on every machine.

+../_images/pip.png +

Committed to CGNS standard

+

maia.pytree works with nothing more than the standard +CGNS/Python mapping +which makes it easily interoperable. +In addition, it exploits the +CGNS/SIDS description +to propose relevant actions on specific nodes.

+../_images/sids_warning.png +

Modern python

+

We hope that type hints, detailled docstrings and adhesion to PEP8 guidelines +will make your user experience of maia.pytree as pleasant as possible.

+../_images/vscode_hints.png +

Code quality

+

Thoroughly tested functionalities, continuous integration setup and automatically +tested documentation make maia.pytree as reliable as possible.

+../_images/badges.svg
+

Note

+

This module is still under construction. We are trying our best to fulfill the above goals. +Contributions and feedbacks are welcomed on +Gitlab !

+
+

Using the documentation

+

This documentation serves as an API reference but also as a tutorial to explain +the different features of maia.pytree.

+

Unless otherwise specified, all the described +functions are accessible from the module namespace, shortened as PT:

+
import maia.pytree as PT
+
+
+

Code snippets appearing in this +documentation follow the doctest formatting:

+
>>> PT.__name__   # Command prompted
+'maia.pytree'     # Expected output
+
+
+

The features are regrouped in the following sections:

+ +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/maia_pytree/search.html b/docs/1.3/maia_pytree/search.html new file mode 100644 index 00000000..76b24df1 --- /dev/null +++ b/docs/1.3/maia_pytree/search.html @@ -0,0 +1,890 @@ + + + + + + + + + + Node searching — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Node searching

+

It is often necessary to select one or several nodes matching a specific +condition in a CGNSTree. maia.pytree provides various functions to +do that, depending of what is needed.

+
+

Tutorial

+

Almost all the functions have the following pattern:

+
+
+get_node(s)_from_{predicate}(s)(node, condition, **kwargs)
+
+ +

node has the same meaning for all the search functions, and is +simply the tree in which the search is performed. +Then, by choosing appropriate keyword for {predicate} and using +or not the s suffixes, lot of cases can be covered.

+

To illustrate the different possibilities, let’s consider the following example +tree:

+
ZoneBC ZoneBC_t
+├───BC1 BC_t
+│   ├───GridLocation GridLocation_t "Vertex"
+│   ├───PointRange IndexRange_t [[ 1 11] [ 1  1]]
+│   └───BCDataSet BCDataSet_t "Null"
+│       ├───PointRange IndexRange_t [[1 5] [1 1]]
+│       └───GridLocation GridLocation_t "FaceCenter"
+└───BC2 BC_t
+    ├───GridLocation GridLocation_t "Vertex"
+    └───PointRange IndexRange_t [[ 1 11] [ 6  6]]
+
+
+
+

Search condition

+

The first customizable thing is {predicate}, which indicates +which criteria is applied to every node to select it or not. +The condition is the concrete value of the criteria:

+ ++++ + + + + + + + + + + + + + + + + + + + +

Function

Condition kind

get_..._from_name()

str

get_..._from_label()

str

get_..._from_value()

value

get_..._from_predicate()

function

+

The first functions are easy to understand: note just here that +from_name and from_label accept wildcards * in their condition.

+

The last one is the more +general and take as predicate a function, which will be applied +to the nodes of the tree, and must return True (node is selected) +or False (node is not selected) : f(n:CGNSTree) -> bool

+

Here is an example of these functions:

+
>>> PT.get_node_from_name(node, 'BC2')
+# Return node BC2
+>>> PT.get_node_from_label(node, 'BC_t')
+# Return node BC1
+>>> PT.get_node_from_value(node, [[1,5],[1,1]])
+# Return the PointRange node located under BCDataSet
+>>> PT.get_node_from_predicate(node, lambda n: 'BC' in PT.get_label(n)
+...    and PT.Subset.GridLocation(n) == 'FaceCenter')
+# Return node BCDataSet
+
+
+
+

See also

+

There is also a get_..._from_name_and_label() form, which takes two str +as condition: first one is for the name, second one for the label.

+
>>> PT.get_node_from_name_and_label(node, '*', '*Location_t')
+# Return node GridLocation of BC1
+
+
+
+
+
+

Number of results

+

The second customizable thing is the s after node, which can be used to +decide if the search will return the first node matching the +predicate, or all the nodes matching the predicate:

+ ++++ + + + + + + + + + + + + + +

Function

Return

get_node_from_...()

First node found or None

get_nodes_from_...()

List of all the nodes found or []

+
>>> PT.get_node_from_label(node, 'BC_t')
+# Return node BC1
+>>> PT.get_nodes_from_label(node, 'BC_t')
+# Return a list containing BC1, BC2
+>>> PT.get_node_from_label(node, 'DataArray_t')
+# Return None
+>>> PT.get_nodes_from_label(node, 'DataArray_t')
+# Return an empty list
+
+
+
+

See also

+

All the get_nodes_from_...() functions have a +iter_nodes_from_...() variant, which return a generator instead of a list +and can be used for looping

+
+
+
+

Chaining searches (advanced)

+

When looking for a particular node, it is often necessary to chain several +searches. For example, if we want to select the BCData nodes under a +BCDataSet node, we can do

+
>>> for bcds in PT.get_nodes_from_label(node, 'BCDataSet_t'):
+>>>   for bcdata in PT.get_nodes_from_label(bcds, 'BCData_t'):
+>>>     # Do something with bcdata nodes
+
+
+

This code can be reduced to a single function call by just adding a s to the search +function, and changing the condition to a list :

+
>>> for bcdata in PT.get_nodes_from_labels(node, ['BCDataSet_t', 'BCData_t']):
+>>>   # Do something with bcdata nodes
+
+
+

Just as before, a search will be performed starting from node, using the first +condition; from the results, a second search will be performed using the second +condition; and so on.

+
+

Tip

+

Since it can be cumbersome to write manually several predicate functions, +the get_..._from_predicates() come with “autopredicate” feature: +the list of functions can be replaced by a string, which is converted from +the following steps:

+
    +
  • String is split from separator '/'

  • +
  • Each substring is replaced by get_label if it ends with _t, +and by get_name otherwise.

  • +
+
>>> for bcdata in PT.get_nodes_from_predicates(node, 'BCDataSet_t/BCData_t'):
+>>>   # Do something wih bcdata nodes
+
+
+
+

Note that the generic versions get_..._from_predicates() expect a list +of predicate functions as condition, and can thus be used to mix the kind +of criteria used to compare at each level :

+
>>> is_bc = lambda n : PT.get_label(n) == 'BC_t' and
+...                    PT.get_node_from_label(n, 'BCDataSet_t') is None
+>>> is_pr = lambda n : PT.get_name(n) == 'PointRange'
+>>> for pr in PT.get_nodes_from_predicates(node, [is_bc, is_pr]):
+>>>   # Do something with pr nodes
+
+
+
+
+

Fine tuning searches

+

Here is a selection of the most usefull kwargs accepted by the functions. +See API reference for the full list.

+
    +
  • depth (integer): Apply to all the functions

    +

    Restrict the search to the nth level of children, where level 0 is the input node itself. +If unset, search is not restricted.

    +
    +

    Tip

    +

    Limiting the search to a single level is so common that all the functions +have a specific variant to do that :

    +
      +
    • get_child_from_...() means get_node_from_...() with depth==1

    • +
    • get_children_from_...() means get_nodes_from_...() with depth==1

    • +
    +
    +
    >>> PT.get_nodes_from_label(node, 'GridLocation_t')
    +# Return the 3 GridLocation nodes
    +>>> PT.get_nodes_from_label(node, 'GridLocation_t', depth=2)
    +# Return the 2 GridLocation nodes under the BC nodes
    +
    +
    +
  • +
  • explore (‘shallow’ or ‘deep’): Apply to get_nodes_from_… functions

    +

    If explore == ‘shallow’, the children of a node matching the predicate +are not tested. If explore=’deep’, all the nodes are tested. +Default is ‘shallow’.

    +
    >>> PT.get_nodes_from_label(node, 'BC*_t')
    +# Return nodes BC1 and BC2
    +>>> PT.get_nodes_from_label(node, 'BC*_t', explore='deep')
    +# Return nodes BC1, BCDataSet and BC2
    +
    +
    +
  • +
  • ancestors (bool): Advanced – Apply to get_…_from_predicates functions

    +

    If True, return tuple of nodes instead of the terminal node. Tuple is of size +len(conditions) and contains all the intermediate results. Default is False.

    +
    >>> for bc, loc in PT.get_nodes_from_predicates(node,
    +...                                             'BC_t/GridLocation_t',
    +...                                             ancestors=True):
    +...   print(PT.get_name(bc), PT.get_value(loc))
    +BC1 Vertex
    +BC1 FaceCenter
    +BC2 Vertex
    +
    +
    +
  • +
+
+
+
+

Summary

+

Here is an overview of the available searching functions:

+ + ++++++ + + + + + + + + + + + + + + + + + + + +
Generic version of search functions

Return first match

Return all matches

Iterate all matches

Single predicate

get_node_from_predicate()

get_nodes_from_predicate()

iter_nodes_from_predicate()

Multiple predicates

get_node_from_predicates()

get_nodes_from_predicates()

iter_nodes_from_predicates()

+ + ++++++ + + + + + + + + + + + + + + + + + + + +
Specialized versions of search functions

Return first match

Return all matches

Iterate all matches

Single predicate

get_node_from_name() +get_node_from_label() +get_node_from_value() +get_node_from_name_and_label() +get_child_from_name() +get_child_from_label() +get_child_from_value() +get_child_from_name_and_label()

get_nodes_from_name() +get_nodes_from_label() +get_nodes_from_value() +get_nodes_from_name_and_label() +get_children_from_name() +get_children_from_label() +get_children_from_value() +get_children_from_name_and_label()

iter_nodes_from_name() +iter_nodes_from_label() +iter_nodes_from_value() +iter_nodes_from_name_and_label() +iter_children_from_name() +iter_children_from_label() +iter_children_from_value() +iter_children_from_name_and_label()

Multiple predicates

get_node_from_names() +get_node_from_labels() +get_node_from_values() +get_node_from_name_and_labels() +get_child_from_names() +get_child_from_labels() +get_child_from_values() +get_child_from_name_and_labels()

get_nodes_from_names() +get_nodes_from_labels() +get_nodes_from_values() +get_nodes_from_name_and_labels() +get_children_from_names() +get_children_from_labels() +get_children_from_values() +get_children_from_name_and_labels()

iter_nodes_from_names() +iter_nodes_from_labels() +iter_nodes_from_values() +iter_nodes_from_name_and_labels() +iter_children_from_names() +iter_children_from_labels() +iter_children_from_values() +iter_children_from_name_and_labels()

+

The following functions do not directly derive from the previous one, +but allow additional usefull searches:

+ ++++ + + + + + + + + + + + +

get_node_from_path(root, path)

Return the node in input tree matching given path, or None

get_all_CGNSBase_t(root)

Return the list of all the CGNSBase_t nodes found in input tree

get_all_Zone_t(root)

Return the list of all the Zone_t nodes found in input tree

+
+
+

API reference

+
+
+get_node_from_predicate(root, predicate, **kwargs)
+

Return the first node in input tree matching the given predicate, or None

+

The search can be fine-tuned with the following kwargs:

+
    +
  • depth (int or pair of int): limit the search between the depths minD and +maxD, 0 beeing the input node itself and None meaning unlimited. +If a single int is provided, it is assigned to maxD. +Defaults to (0,None).

  • +
  • search (str): use a Depth-First-Search ('dfs') or +Breath-First-Search ('bfs') algorithm. Defaults to 'dfs'.

  • +
+
+
Parameters
+
    +
  • root (CGNSTree) – Tree is which the search is performed

  • +
  • predicate (callable) – condition to select node, which must +have the following signature: f(n:CGNSTree) -> bool

  • +
  • **kwargs – Additional options (see above)

  • +
+
+
Returns
+

CGNSTree or None – Node found

+
+
+
+

Note

+

This function admits the following shorcuts:

+
    +
  • get_node_from_name|label|value|name_and_label() (embedded predicate)

  • +
  • get_child_from_name|label|value|name_and_label() (embedded predicate + depth=[1,1])

  • +
+
+
+ +
+
+get_nodes_from_predicate(root, predicate, **kwargs)
+

Return the list of all nodes in input tree matching the given predicate

+

The search can be fine-tuned with the following kwargs:

+
    +
  • depth (int or pair of int): see get_node_from_predicate()

  • +
  • search (str): see get_node_from_predicate()

  • +
  • explore (str): Explore the whole tree ('deep') or stop exploring the current branch +once predicate is satisfied ('shallow'). Defaults to 'shallow'.

  • +
+
+
Parameters
+
    +
  • root (CGNSTree) – Tree is which the search is performed

  • +
  • predicate (callable) – condition to select node, which must +have the following signature: f(n:CGNSTree) -> bool

  • +
  • **kwargs – Additional options (see above)

  • +
+
+
Returns
+

list of CGNSTree – Nodes found

+
+
+
+

Note

+

This function admits the following shorcuts:

+
    +
  • get_nodes_from_name|label|value|name_and_label() (embedded predicate)

  • +
  • get_children_from_name|label|value|name_and_label() (embedded predicate + depth=[1,1])

  • +
+
+
+ +
+
+iter_nodes_from_predicate(root, predicate, **kwargs)
+

Iterator version of get_nodes_from_predicate()

+
+

Note

+

This function admits the following shorcuts:

+
    +
  • iter_nodes_from_name|label|value|name_and_label() (embedded predicate)

  • +
  • iter_children_from_name|label|value|name_and_label() (embedded predicate + depth=[1,1])

  • +
+
+
+ +
+
+get_node_from_predicates(root, predicates, **kwargs)
+

Return the first node in input tree matching the chain of predicates, or None

+

The search can be fine-tuned with the following kwargs:

+ +
+
Parameters
+
    +
  • root (CGNSTree) – Tree is which the search is performed

  • +
  • predicates (list of callable) – conditions to select next node, each one +having the following signature: f(n:CGNSTree) -> bool

  • +
  • **kwargs – Additional options (see above)

  • +
+
+
Returns
+

CGNSTree or None – Node found

+
+
+
+

Note

+

This function admits the following shorcuts:

+
    +
  • get_node_from_names|labels|values|name_and_labels() (embedded predicate)

  • +
  • get_child_from_names|labels|values|name_and_labels() (embedded predicate + depth=[1,1])

  • +
+
+
+ +
+
+get_nodes_from_predicates(root, predicates, **kwargs)
+

Return the list of all nodes in input tree matching the chain of predicates

+

The search can be fine-tuned with the following kwargs:

+ +
+
Parameters
+
    +
  • root (CGNSTree) – Tree is which the search is performed

  • +
  • predicates (list of callable) – conditions to select next node, each one +having the following signature: f(n:CGNSTree) -> bool

  • +
  • **kwargs – Additional options (see above)

  • +
+
+
Returns
+

list of CGNSTree – Nodes found

+
+
+
+

Note

+

This function admits the following shorcuts:

+
    +
  • get_nodes_from_names|labels|values|name_and_labels() (embedded predicate)

  • +
  • get_children_from_names|labels|values|name_and_labels() (embedded predicate + depth=[1,1])

  • +
+
+
+ +
+
+iter_nodes_from_predicates(root, predicates, **kwargs)
+

Iterator version of get_nodes_from_predicates()

+
+

Note

+

This function admits the following shorcuts:

+
    +
  • iter_nodes_from_names|labels|values|name_and_labels() (embedded predicate)

  • +
  • iter_children_from_names|labels|values|name_and_labels() (embedded predicate + depth=[1,1])

  • +
+
+
+ +
+
+get_node_from_path(root, path)
+

Return the node in input tree matching given path, or None

+

A path is a str containing a full list of names, separated by '/', leading +to the node to select. Root name should not be included in path. +Wildcards are not accepted in path.

+
+
Parameters
+
    +
  • root (CGNSTree) – Tree is which the search is performed

  • +
  • path (str) – path of the node to select

  • +
+
+
Returns
+

CGNSTree or None – Node found

+
+
+

Example

+
>>> zone = PT.yaml.parse_yaml_cgns.to_node('''
+... Zone Zone_t:
+...   ZoneBC ZoneBC_t:
+...     BC BC_t "Null":
+...       GridLocation GridLocation_t "Vertex":
+... ''')
+>>> PT.get_node_from_path(zone, 'ZoneBC/BC/GridLocation')
+# Return node GridLocation
+>>> PT.get_node_from_path(zone, 'ZoneBC/BC/PointRange')
+# Return None
+
+
+
+ +
+
+get_all_CGNSBase_t(root)
+

Return the list of all the CGNSBase_t nodes found in input tree

+

This function is SIDS aware, and will only search nodes in relevant places.

+
+
Parameters
+

root (CGNSTree) – Tree is which the search is performed

+
+
Returns
+

list of CGNSTree – Nodes found

+
+
+
+

See also

+

This function has the iterator counterpart iter_all_CGNSBase_t()

+
+

Example

+
>>> tree = PT.yaml.parse_yaml_cgns.to_cgns_tree('''
+... BaseA CGNSBase_t:
+...   Zone1 Zone_t:
+...   Zone2 Zone_t:
+... BaseB CGNSBase_t:
+...   Zone3 Zone_t:
+... ''')
+>>> [PT.get_name(n) for n in PT.get_all_CGNSBase_t(tree)]
+['BaseA', 'BaseB']
+
+
+
+ +
+
+get_all_Zone_t(root)
+

Return the list of all the Zone_t nodes found in input tree

+

This function is SIDS aware, and will only search nodes in relevant places.

+
+
Parameters
+

root (CGNSTree) – Tree is which the search is performed

+
+
Returns
+

list of CGNSTree – Nodes found

+
+
+
+

See also

+

This function has the iterator counterpart iter_all_Zone_t()

+
+

Example

+
>>> tree = PT.yaml.parse_yaml_cgns.to_cgns_tree('''
+... BaseA CGNSBase_t:
+...   Zone1 Zone_t:
+...   Zone2 Zone_t:
+... BaseB CGNSBase_t:
+...   Zone3 Zone_t:
+... ''')
+>>> [PT.get_name(n) for n in PT.iter_all_Zone_t(tree)]
+['Zone1', 'Zone2', 'Zone3']
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/objects.inv b/docs/1.3/objects.inv new file mode 100644 index 00000000..5bc7781b Binary files /dev/null and b/docs/1.3/objects.inv differ diff --git a/docs/1.3/py-modindex.html b/docs/1.3/py-modindex.html new file mode 100644 index 00000000..157e8907 --- /dev/null +++ b/docs/1.3/py-modindex.html @@ -0,0 +1,274 @@ + + + + + + + + + + Python Module Index — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Python Module Index
  • + + +
  • + +
  • + +
+ + +
+
+
+
+ + +

Python Module Index

+ +
+ m +
+ + + + + + + + + + +
 
+ m
+ maia +
    + maia.pytree.node.presets +
+ + +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/quick_start.html b/docs/1.3/quick_start.html new file mode 100644 index 00000000..8f1733da --- /dev/null +++ b/docs/1.3/quick_start.html @@ -0,0 +1,438 @@ + + + + + + + + + + Quick start — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Quick start

+
+

Environnements

+

Maia is now distributed in elsA releases (since v5.2.01) !

+

If you want to try the latest features, we provide ready-to-go environments including Maia and its dependencies on the following clusters:

+

Spiro-EL8

+

This is the recommended environment for standalone usage of Maia. It works with intel mpi library (2021) +and python version 3.9.

+
source /scratchm/sonics/dist/source.sh --env maia
+module load maia/dev-default
+
+
+

If you want to use Maia within the standard Spiro environment, the next installation is compatible with +the socle socle-cfd/5.0-intel2120-impi:

+
module use --append /scratchm/sonics/usr/modules/
+module load maia/dev-dsi-cfd5
+
+
+

Note that this is the environment used by elsA for its production spiro-el8_mpi.

+

Sator

+

Similarly, Maia installation are available in both the self maintained and standard socle +on Sator cluster. Sator’s version is compiled with support of large integers.

+
# Versions based on self compiled tools
+source /tmp_user/sator/sonics/dist/source.sh --env maia
+module load maia/dev-default
+
+# Versions based on socle-cfd compilers and tools
+module use --append /tmp_user/sator/sonics/usr/modules/
+module load maia/dev-dsi-cfd5
+
+
+

If you prefer to build your own version of Maia, see Installation section.

+
+
+

Supported meshes

+

Maia supports CGNS meshes from version 4.2, meaning that polyhedral connectivities (NGON_n, NFACE_n +and MIXED nodes) must have the ElementStartOffset node.

+

Former meshes can be converted with the (sequential) maia_poly_old_to_new script included +in the $PATH once the environment is loaded:

+
$> maia_poly_old_to_new mesh_file.hdf
+
+
+

The opposite maia_poly_new_to_old script can be used to put back meshes +in old conventions, insuring compatibility with legacy tools.

+
+

Warning

+

CGNS databases should respect the SIDS. +The most commonly observed non-compliant practices are:

+
    +
  • Empty DataArray_t (of size 0) under FlowSolution_t containers.

  • +
  • 2D shaped (N1,N2) DataArray_t under BCData_t containers. +These arrays should be flat (N1xN2,).

  • +
  • Implicit BCDataSet_t location for structured meshes: if GridLocation_t +and PointRange_t of a given BCDataSet_t differs from the +parent BC_t node, theses nodes should be explicitly defined at BCDataSet_t +level.

  • +
+

Several non-compliant practices can be detected with the cgnscheck utility. Do not hesitate +to check your file if Maia is unable to read it.

+
+

Note also that ADF files are not supported; CGNS files should use the HDF binary format. ADF files can +be converted to HDF thanks to cgnsconvert.

+
+
+

Highlights

+
+

Tip

+

Download sample files of this section: +S_twoblocks.cgns, +U_ATB_45.cgns

+
+

Daily user-friendly pre & post processing

+

Maia provides simple Python APIs to easily setup pre or post processing operations: +for example, converting a structured tree into an unstructured (NGon) tree +is as simple as

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia
+
+tree_s = maia.io.file_to_dist_tree('S_twoblocks.cgns', comm)
+tree_u = maia.algo.dist.convert_s_to_ngon(tree_s, comm)
+maia.io.dist_tree_to_file(tree_u, 'U_twoblocks.cgns', comm)
+
+
+_images/qs_basic.png +

In addition of being parallel, the algorithms are as much as possible topologic, meaning that +they do not rely on a geometric tolerance. This also allow us to preserve the boundary groups +included in the input mesh (colored on the above picture).

+

Building efficient workflows

+

By chaining this elementary blocks, you can build a fully parallel advanced workflow +running as a single job and minimizing file usage.

+

In the following example, we load an angular section of the +ATB case, +duplicate it to a 180° case, split it, and perform some slices and extractions.

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia.pytree as PT
+import maia
+
+# Read the file. Tree is distributed
+dist_tree = maia.io.file_to_dist_tree('U_ATB_45.cgns', comm)
+
+# Duplicate the section to a 180° mesh
+# and merge all the blocks into one
+opposite_jns = [['Base/bump_45/ZoneGridConnectivity/matchA'],
+                ['Base/bump_45/ZoneGridConnectivity/matchB']]
+maia.algo.dist.duplicate_from_periodic_jns(dist_tree,
+    ['Base/bump_45'], opposite_jns, 22, comm)
+maia.algo.dist.merge_connected_zones(dist_tree, comm)
+
+# Split the mesh to have a partitioned tree
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Now we can call some partitioned algorithms
+maia.algo.part.compute_wall_distance(part_tree, comm, point_cloud='Vertex')
+extract_tree = maia.algo.part.extract_part_from_bc_name(part_tree, "wall", comm)
+slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0], comm,
+      containers_name=['WallDistance'])
+
+# Merge extractions in a same tree in order to save it
+base = PT.get_child_from_label(slice_tree, 'CGNSBase_t')
+PT.set_name(base, f'PlaneSlice')
+PT.add_child(extract_tree, base)
+maia.algo.pe_to_nface(dist_tree,comm)
+
+extract_tree_dist = maia.factory.recover_dist_tree(extract_tree, comm)
+maia.io.dist_tree_to_file(extract_tree_dist, 'ATB_extract.cgns', comm)
+
+
+_images/qs_workflow.png +

The above illustration represents the input mesh (gray volumic block) and the +extracted surfacic tree (plane slice and extracted wall BC). Curved lines are +the outline of the volumic mesh after duplication.

+

Compliant with the pyCGNS world

+

Finally, since Maia uses the standard CGNS/Python mapping, +you can set up applications involving multiple python packages: +here, we create and split a mesh with maia, but we then call Cassiopee functions +to compute the gradient of a field on each partition.

+
from   mpi4py.MPI import COMM_WORLD as comm
+import maia
+import Transform.PyTree as CTransform
+import Converter.PyTree as CConverter
+import Post.PyTree      as CPost
+
+dist_tree = maia.factory.generate_dist_block([101,6,6], 'TETRA_4', comm)
+CTransform._scale(dist_tree, [5,1,1], X=(0,0,0))
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+CConverter._initVars(part_tree, '{Field}=sin({nodes:CoordinateX})')
+part_tree = CPost.computeGrad(part_tree, 'Field')
+
+maia.transfer.part_tree_to_dist_tree_all(dist_tree, part_tree, comm)
+maia.io.dist_tree_to_file(dist_tree, 'out.cgns', comm)
+
+
+_images/qs_pycgns.png +

Be aware that other tools often expect to receive geometrically +consistent data, which is why we send the partitioned tree to +Cassiopee functions. The outline of this partitions (using 4 processes) +are materialized by the white lines on the above figure.

+
+
+

Resources and Troubleshouting

+

The user manual describes most of the high level APIs provided by Maia. +If you want to be comfortable with the underlying concepts of distributed and +partitioned trees, have a look at the introduction section.

+

The user manual is illustrated with basic examples. Additional +test cases can be found in +the sources.

+

Issues can be reported on +the gitlab board +and help can also be asked on the dedicated +Element room.

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/related_projects.html b/docs/1.3/related_projects.html new file mode 100644 index 00000000..9b63f84c --- /dev/null +++ b/docs/1.3/related_projects.html @@ -0,0 +1,283 @@ + + + + + + + + + + Related projects — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/releases/release_notes.html b/docs/1.3/releases/release_notes.html new file mode 100644 index 00000000..a9c00342 --- /dev/null +++ b/docs/1.3/releases/release_notes.html @@ -0,0 +1,414 @@ + + + + + + + + + + Release notes — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Release notes

+

This page contains information about what has changed in each new version of Maia.

+
+

v1.3 (January 2024)

+
+

💡 New Features

+
    +
  • Algo module: add extract_part_from_family API to extract a submesh from FamilyName nodes

  • +
  • Algo module: add scale_mesh to scale the coordinates of a cartesian mesh

  • +
  • Algo module: add nodes_to_centers to interpolate FlowSolution_t from Vertex to CellCenter

  • +
+
+
+

🚀 Feature improvements

+
    +
  • connect_1to1_families: manage Elements meshes

  • +
  • extract_part_from_xxx: transfer BCs on extracted meshes and manage structured meshes

  • +
  • partitioning: enable split of 2D/1D structured meshes

  • +
  • interpolation: allow input fields to be Vertex located in some cases

  • +
  • poly_old_to_new / poly_new_to_old : manage mixed elements

  • +
  • convert_elements_to_ngon : manage mixed elements

  • +
  • adapt_mesh_with_feflo: add an option to manage periodic meshes

  • +
+
+
+

🐞 Fixes

+
    +
  • merge_zones: manage S/U GridConnectivity_t when merging U zones

  • +
  • add_joins_donor_name: prevent a crash when some GCs already have their DonorName

  • +
  • transform_affine : manage partitioned S zones and 2D meshes

  • +
  • transfer module : prevent a bug occurring when subset nodes have a dot in their name

  • +
  • convert_mixed_to_elements: prevent a bug occurring when multiple MIXED nodes are used

  • +
  • [v1.3.1] io: fix read of unstructured BCDataSet having a PointList

  • +
+
+
+

🚧 API change

+
    +
  • extract_part_from_zsr: add transfert_dataset argument for easier transfer of current ZSR fields

  • +
  • convert_s_to_u: operate inplace (input tree is modified). Will return None in next release.

  • +
+
+
+
+

v1.2 (July 2023)

+
+

💡 New Features

+
    +
  • Algo module: add adapt_mesh_with_feflo, wrapping Feflo.a to perform mesh adaptation

  • +
  • Factory module : add dist_to_full_tree to gather a distributed tree into a standard tree

  • +
  • File management: add read_links function to get the links from a CGNS file

  • +
  • File management: add file_to_part_tree function to read maia partitioned trees

  • +
+
+
+

🚀 Feature improvements

+
    +
  • file_to_dist_tree: correct unsigned NFace connectivity if possible

  • +
  • wall_distance: add an option to take into account periodic connectivities

  • +
  • poly_old_to_new / poly_new_to_old : support 2D meshes

  • +
+
+
+

🐞 Fixes

+
    +
  • merge_zones: fix unwanted merge of BCDataSet_t when merge_strategy is None

  • +
  • partitioning: fix global numbering of S BCDataSet + fix GC-related ZGC

  • +
  • isosurface: fix poor performances + better management of corner cases

  • +
  • distributed io: fix read/write of S meshes for data smaller than comm size

  • +
  • elements to ngon conversion: manage vertex located BCs

  • +
+
+
+

🚧 API change

+
    +
  • redistribute_tree: remove default value for policy

  • +
  • wall_distance: remove families parameter

  • +
  • distribute_tree renamed into full_to_dist_tree

  • +
+
+
+

🔧 Advanced users / devs

+
    +
  • Add a method to give a global id to any object in parallel

  • +
+
+
+
+

v1.1 (May 2023)

+
+

💡 New Features

+
    +
  • Algo module: generate (periodic) 1to1 GridConnectivity between selected BC or GC

  • +
  • Factory module: generate 2D spherical meshes and points clouds

  • +
+
+
+

🚀 Feature improvements

+
    +
  • generate_dist_block: enable generation of structured meshes

  • +
  • partitioning: enable split of 2D (NGON/Elts) and 1D (Elts) meshes

  • +
  • partitioning: copy AdditionalFamilyName and ReferenceState from BCs to the partitions

  • +
  • compute_face_center : manage structured meshes

  • +
  • merge_zones: allow wildcards in zone_paths

  • +
  • isosurface: recover volumic GCs on surfacic tree (as BCs)

  • +
  • transfer (part->dist): manage BC/BCDataSet created on partitions for structured meshes

  • +
+
+
+

🐞 Fixes

+
    +
  • convert_elements_to_ngon: prevent a memory error & better management of 2D meshes

  • +
  • isosurface: improve robustness of edge reconstruction

  • +
  • partitioning: fix split of structured GCs and BCDataSet

  • +
  • merge_zone: fix a bug occurring when FamilyName appears under some BC_t nodes

  • +
+
+
+

🔧 Advanced users / devs

+
    +
  • use new pytest_parallel module

  • +
  • transfer (part->dist): add user callback to reduce shared entities

  • +
+
+
+
+

v1.0 (March 2023)

+

First release of Maia !

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + diff --git a/docs/1.3/search.html b/docs/1.3/search.html new file mode 100644 index 00000000..b8648562 --- /dev/null +++ b/docs/1.3/search.html @@ -0,0 +1,269 @@ + + + + + + + + + + Search — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
    + +
  • »
  • + +
  • Search
  • + + +
  • + +
  • + +
+ + +
+
+
+
+ + + + +
+ +
+ +
+ +
+
+ +
+ +
+

+ © Copyright 2021, ONERA The French Aerospace Lab. + +

+
+ + + + Built with Sphinx using a + + theme + + provided by Read the Docs. + +
+
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/searchindex.js b/docs/1.3/searchindex.js new file mode 100644 index 00000000..7226f25f --- /dev/null +++ b/docs/1.3/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["developer_manual/algo_description","developer_manual/algo_description/elements_to_ngons","developer_manual/developer_manual","developer_manual/logging","developer_manual/maia_dev/conventions","developer_manual/maia_dev/development_workflow","index","installation","introduction/introduction","license","maia_pytree/basic","maia_pytree/inspect","maia_pytree/presets","maia_pytree/pytree_module","maia_pytree/search","quick_start","related_projects","releases/release_notes","user_manual/algo","user_manual/config","user_manual/factory","user_manual/io","user_manual/transfer","user_manual/user_manual"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["developer_manual/algo_description.rst","developer_manual/algo_description/elements_to_ngons.rst","developer_manual/developer_manual.rst","developer_manual/logging.rst","developer_manual/maia_dev/conventions.rst","developer_manual/maia_dev/development_workflow.rst","index.rst","installation.rst","introduction/introduction.rst","license.rst","maia_pytree/basic.rst","maia_pytree/inspect.rst","maia_pytree/presets.rst","maia_pytree/pytree_module.rst","maia_pytree/search.rst","quick_start.rst","related_projects.rst","releases/release_notes.rst","user_manual/algo.rst","user_manual/config.rst","user_manual/factory.rst","user_manual/io.rst","user_manual/transfer.rst","user_manual/user_manual.rst"],objects:{"":[[20,0,1,"","dump_pdm_output"],[14,1,1,"","get_node"],[20,0,1,"","graph_part_tool"],[20,0,1,"","part_interface_loc"],[20,0,1,"","preserve_orientation"],[20,0,1,"","reordering"],[20,0,1,"","zone_to_parts"]],"maia.algo":[[18,1,1,"","nface_to_pe"],[18,1,1,"","pe_to_nface"],[18,1,1,"","scale_mesh"],[18,1,1,"","transform_affine"]],"maia.algo.dist":[[18,1,1,"","adapt_mesh_with_feflo"],[18,1,1,"","conformize_jn_pair"],[18,1,1,"","connect_1to1_families"],[18,1,1,"","convert_elements_to_mixed"],[18,1,1,"","convert_elements_to_ngon"],[18,1,1,"","convert_mixed_to_elements"],[18,1,1,"","convert_s_to_u"],[18,1,1,"","duplicate_from_rotation_jns_to_360"],[18,1,1,"","generate_jns_vertex_list"],[18,1,1,"","merge_connected_zones"],[18,1,1,"","merge_zones"],[18,1,1,"","merge_zones_from_family"],[18,1,1,"","ngons_to_elements"],[18,1,1,"","rearrange_element_sections"],[18,1,1,"","redistribute_tree"]],"maia.algo.part":[[18,1,1,"","centers_to_nodes"],[18,1,1,"","compute_cell_center"],[18,1,1,"","compute_edge_center"],[18,1,1,"","compute_face_center"],[18,1,1,"","compute_wall_distance"],[18,1,1,"","extract_part_from_bc_name"],[18,1,1,"","extract_part_from_family"],[18,1,1,"","extract_part_from_zsr"],[18,1,1,"","find_closest_points"],[18,1,1,"","interpolate_from_part_trees"],[18,1,1,"","iso_surface"],[18,1,1,"","localize_points"],[18,1,1,"","nodes_to_centers"],[18,1,1,"","plane_slice"],[18,1,1,"","spherical_slice"]],"maia.algo.seq":[[18,1,1,"","enforce_ngon_pe_local"],[18,1,1,"","poly_new_to_old"],[18,1,1,"","poly_old_to_new"]],"maia.factory":[[20,1,1,"","dist_to_full_tree"],[20,1,1,"","full_to_dist_tree"],[20,1,1,"","generate_dist_block"],[20,1,1,"","generate_dist_points"],[20,1,1,"","generate_dist_sphere"],[20,1,1,"","partition_dist_tree"],[20,1,1,"","recover_dist_tree"]],"maia.factory.partitioning":[[20,1,1,"","compute_balanced_weights"],[20,1,1,"","compute_nosplit_weights"],[20,1,1,"","compute_regular_weights"]],"maia.io":[[21,1,1,"","dist_tree_to_file"],[21,1,1,"","file_to_dist_tree"],[21,1,1,"","file_to_part_tree"],[21,1,1,"","part_tree_to_file"],[21,1,1,"","read_links"],[21,1,1,"","read_tree"],[21,1,1,"","write_tree"],[21,1,1,"","write_trees"]],"maia.pytree":[[11,2,1,"","Element"],[11,2,1,"","GridConnectivity"],[11,2,1,"","Subset"],[11,2,1,"","Tree"],[11,2,1,"","Zone"],[10,1,1,"","add_child"],[14,1,1,"","get_all_CGNSBase_t"],[14,1,1,"","get_all_Zone_t"],[10,1,1,"","get_children"],[10,1,1,"","get_label"],[10,1,1,"","get_name"],[14,1,1,"","get_node_from_path"],[14,1,1,"","get_node_from_predicate"],[14,1,1,"","get_node_from_predicates"],[14,1,1,"","get_nodes_from_predicate"],[14,1,1,"","get_nodes_from_predicates"],[10,1,1,"","get_value"],[10,1,1,"","get_value_kind"],[10,1,1,"","get_value_type"],[14,1,1,"","iter_nodes_from_predicate"],[14,1,1,"","iter_nodes_from_predicates"],[10,1,1,"","new_child"],[10,1,1,"","new_node"],[10,1,1,"","print_tree"],[10,1,1,"","set_children"],[10,1,1,"","set_label"],[10,1,1,"","set_name"],[10,1,1,"","set_value"],[10,1,1,"","update_child"],[10,1,1,"","update_node"]],"maia.pytree.Element":[[11,3,1,"","CGNSName"],[11,3,1,"","Dimension"],[11,3,1,"","NVtx"],[11,3,1,"","Range"],[11,3,1,"","Size"],[11,3,1,"","Type"]],"maia.pytree.GridConnectivity":[[11,3,1,"","Transform"],[11,3,1,"","Type"],[11,3,1,"","ZoneDonorPath"],[11,3,1,"","is1to1"],[11,3,1,"","isperiodic"],[11,3,1,"","periodic_values"]],"maia.pytree.Subset":[[11,3,1,"","GridLocation"],[11,3,1,"","ZSRExtent"],[11,3,1,"","getPatch"],[11,3,1,"","n_elem"],[11,3,1,"","normal_axis"]],"maia.pytree.Tree":[[11,3,1,"","find_connected_zones"],[11,3,1,"","find_periodic_jns"]],"maia.pytree.Zone":[[11,3,1,"","CellDimension"],[11,3,1,"","CellSize"],[11,3,1,"","FaceSize"],[11,3,1,"","IndexDimension"],[11,3,1,"","NFaceNode"],[11,3,1,"","NGonNode"],[11,3,1,"","Type"],[11,3,1,"","VertexBoundarySize"],[11,3,1,"","VertexSize"],[11,3,1,"","coordinates"],[11,3,1,"","elt_ordering_by_dim"],[11,3,1,"","get_elt_range_per_dim"],[11,3,1,"","get_ordered_elements"],[11,3,1,"","get_ordered_elements_per_dim"],[11,3,1,"","has_nface_elements"],[11,3,1,"","has_ngon_elements"],[11,3,1,"","n_cell"],[11,3,1,"","n_face"],[11,3,1,"","n_vtx"],[11,3,1,"","n_vtx_bnd"]],"maia.pytree.node":[[12,4,0,"-","presets"]],"maia.pytree.node.presets":[[12,1,1,"","new_BC"],[12,1,1,"","new_BaseIterativeData"],[12,1,1,"","new_CGNSBase"],[12,1,1,"","new_CGNSTree"],[12,1,1,"","new_DataArray"],[12,1,1,"","new_Elements"],[12,1,1,"","new_Family"],[12,1,1,"","new_FamilyName"],[12,1,1,"","new_FlowSolution"],[12,1,1,"","new_GridConnectivity"],[12,1,1,"","new_GridConnectivity1to1"],[12,1,1,"","new_GridConnectivityProperty"],[12,1,1,"","new_GridCoordinates"],[12,1,1,"","new_GridLocation"],[12,1,1,"","new_IndexArray"],[12,1,1,"","new_IndexRange"],[12,1,1,"","new_NFaceElements"],[12,1,1,"","new_NGonElements"],[12,1,1,"","new_Zone"],[12,1,1,"","new_ZoneBC"],[12,1,1,"","new_ZoneGridConnectivity"],[12,1,1,"","new_ZoneSubRegion"]],"maia.transfer":[[22,1,1,"","dist_tree_to_part_tree_all"],[22,1,1,"","dist_tree_to_part_tree_only_labels"],[22,1,1,"","dist_zone_to_part_zones_all"],[22,1,1,"","dist_zone_to_part_zones_only"],[22,1,1,"","part_tree_to_dist_tree_all"],[22,1,1,"","part_tree_to_dist_tree_only_labels"],[22,1,1,"","part_zones_to_dist_zone_all"],[22,1,1,"","part_zones_to_dist_zone_only"]]},objnames:{"0":["py","attribute","Python attribute"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","module","Python module"]},objtypes:{"0":"py:attribute","1":"py:function","2":"py:class","3":"py:method","4":"py:module"},terms:{"0":[1,3,4,8,10,11,12,14,15,18,19,20,21,22],"00":7,"01":15,"05":[11,12],"1":[1,7,8,10,11,12,14,15,18,20],"10":[8,10,11,12,18,20,21],"100":[11,18],"101":15,"104":12,"10m":20,"11":[9,10,11,12,14,18],"115":11,"12":[8,9,12],"125":12,"13":9,"14":[7,9,10,18],"15":[7,12],"16":10,"170":18,"18":8,"180":[15,18],"19":7,"1d":17,"1e":[11,18],"1to1":[11,17,18],"2":[1,8,10,11,12,14,15,18,20],"20":[11,18,20],"2021":15,"21":[11,18],"22":[12,15],"23":12,"24":8,"25":18,"293":12,"296":12,"2d":[1,11,12,15,17,18,20],"3":[1,7,8,10,11,12,14,15,18,20],"30":[9,11],"31":11,"32":10,"375":20,"3d":[1,18,20],"4":[1,3,5,7,8,11,12,15,18],"40":11,"45":18,"5":[11,12,14,15,18,20],"50":[9,11],"55":11,"6":[7,8,10,11,12,14,15,18],"60":[9,11],"625":20,"7":12,"8":7,"9":[8,12,15],"boolean":18,"case":[4,8,9,10,12,14,15,17,18,20,21],"cassiop\u00e9":7,"class":[3,11,20],"default":[3,7,10,12,14,15,17,18,19,20,21],"do":[3,7,8,9,14,15,21,22,23],"final":[1,9,15],"float":[10,11,12,18,20],"function":[1,3,4,6,8,10,11,12,13,15,17,18,19,20,21,22,23],"import":[1,3,9,13,15,18,20,21,22],"int":[10,11,12,14,20],"long":[3,8],"new":[3,8,10,18,23],"null":[11,12,14],"return":[10,11,12,14,17,18,20,21,23],"short":[8,10,12],"static":11,"true":[1,10,11,14,18,20,21],"try":[3,4,7,8,13,15],"var":4,"while":3,A:[1,3,4,7,8,11,14,16,18,23],As:[1,8],Be:[15,21],But:3,By:[3,8,10,13,15],For:[1,3,4,7,8,9,12,14,18,20,21,22],If:[1,3,7,8,9,10,11,12,14,15,18,20,21],In:[1,7,8,9,10,12,13,15,18,20,21,22],It:[3,5,6,7,8,10,14,15,18,22],Its:8,No:[9,19],On:[7,8,10],One:[1,11],Such:[8,9],The:[1,3,4,5,6,7,8,9,10,11,12,14,15,16,18,19,20,21,22],Their:8,Then:[7,8,14],There:[5,6,14],These:[8,10,11,15,18,22],To:[7,10,11,14],Will:17,With:[1,8],_:1,__name__:13,_all:22,_exchange_field:18,_from_:14,_from_label:14,_from_nam:14,_from_name_and_label:14,_from_pred:14,_from_valu:14,_gn:8,_initvar:15,_onli:22,_scale:15,_t:14,_to:8,_unmatch:18,abbrevi:4,abil:9,abl:[7,9],abort:19,about:[8,17],abov:[8,9,13,14,15,18,20,21],absenc:9,absolut:[8,9,11,20],abut:[11,12],abutting1to1:[11,12],accept:[3,14,18,21],access:[7,8,10,13,18,23],accord:[10,11,18,20,22],accordingli:[1,3,18],account:[1,17,18],accur:9,achiev:[13,21],across:[8,20,22],action:[9,13],activ:18,actual:[8,18],ad:[3,7,14,18,21],adapt:[8,17,18],adapt_mesh_with_feflo:[17,18],add:[1,9,10,12,17,18],add_child:[10,15],add_joins_donor_nam:17,add_logg:3,add_printer_to_logg:3,addfamnam:12,addit:[7,8,10,12,13,14,15,18,21,22],addition:9,additionalfamilynam:[17,18],additionalfamilyname_t:12,address:8,adf:15,adhes:13,admiss:[8,12,18,20],admit:[14,18],adpt_dist_tre:18,advanc:15,advic:[10,18],advis:7,affect:[8,9,20,21],affero:9,affin:18,after:[1,3,9,12,14,15,18],against:9,agre:9,agreement:9,akin:1,algo:[1,15,17,23],algorithm:[2,6,8,14,15,20,23],all:[7,8,9,10,11,12,13,14,15,18,19,20,21,22],all_to_al:1,alleg:9,alloc:1,allow:[8,9,10,14,15,17,18,19,22,23],almost:[14,20],alon:9,alreadi:[8,10,17],also:[1,7,8,9,10,11,12,13,14,15,18,19,21,22],alter:9,altern:[10,20],although:[10,12,18],alwai:[1,8,10,18],among:8,amount:12,an:[1,3,4,6,7,8,9,10,11,12,13,14,15,17,18,19,20],analysi:16,ancestor:14,angl:18,angular:[15,18],ani:[3,8,9,10,11,12,17,18,22],annot:8,anoth:[7,8,23],anyon:9,anyth:3,anytim:3,api:[6,13,15,18,22],apparatu:9,appart:12,appear:[1,8,13,17,18,21],append:[10,15],appli:[1,8,9,11,12,14,18,23],applic:[3,15,19,23],apply_to_field:18,appropri:14,approxim:1,ar:[1,3,6,7,8,9,10,11,12,13,14,15,17,18,19,20,21,22,23],arang:18,arbitrari:8,arboresc:10,architectur:8,argument:[3,10,12,17,18,20],aris:3,arithmet:18,arrai:[1,4,8,10,11,12,15,18,20],arraylik:12,artifact:[5,7],as_addit:12,as_matrix:11,ascend:[8,18],ask:15,assert:[9,18,20,22],assign:14,associ:[1,3,6,8,20],assum:[8,9,22],atb:15,atb_extract:15,atol:11,attach:[3,9,10,12],attempt:9,attribut:10,author:9,automat:[9,13,18,19,20,21],autopred:14,avail:[9,10,11,14,15,18,19,20,21,23],averag:18,avoid:[10,11,13,19],awar:[3,11,14,15,21],ax:18,axi:11,b1:10,b:[7,8,10,18],back:[1,8,9,15],balanc:[1,8,18,20],bar:[11,20],bar_2:[11,12],base:[1,8,10,11,12,15,18,20],basea:[14,18],baseb:[14,18],baseiterativedata:12,baseiterativedata_t:12,basenam:[11,18],basi:[9,10],basic:[8,13,15,18,20],bc1:14,bc2:14,bc:[11,12,14,15,17,18,21],bc_name:[11,12,18],bc_t:[10,12,14,15,17,18],bcd:14,bcdata:14,bcdata_t:[14,15],bcdataset:[14,17,18,20,22],bcdataset_t:[8,14,15,17,18,22],bcnode:10,bcregionnam:12,bcwall:12,becaus:[3,7,8,10],becom:[6,8,9,18],bee:14,been:[7,9,18,20,22],beetween:8,befor:[8,14],begin:[1,3,18],behalf:9,behaviour:[18,19],being:[8,15],believ:9,belong:[8,10,11,18],below:[1,11,12,21],benefici:9,best:[8,13],better:[10,17,18],between:[1,8,14,17,18,22,23],bf:14,binari:15,blk1:18,blk2:18,block:[8,15,18,20],blue:8,bnd2:22,board:15,bool:[10,11,14,18,20,21],both:[3,12,15,18,20,23],bottom:12,bound:8,boundari:[1,11,12,15,18,20],branch:[10,14],breath:[7,14],bring:[9,18],brought:9,bucket:1,bug:17,build:[3,5,15],build_dir:7,built:21,bump_45:15,burden:3,busi:9,bytearrai:10,c1:10,c:[1,3,4,6,8,9,10,18],cacheblock:20,cacheblocking2:20,call:[1,5,8,11,14,15,18,19,21,23],callabl:[10,14],callback:17,can:[1,3,4,5,6,7,8,9,10,11,12,14,15,18,19,20,21],care:12,cartesian:[11,17,18,20],cassiope:[15,16,21],cast:12,caus:[9,18],cconvert:15,ccx:18,ccy:18,ccz:18,cd:[5,7],cdot:[1,18],cell:[1,4,8,11,12,18,20],cell_cent:18,cell_dim:12,cell_fac:4,cell_renum_method:20,cell_sol:18,cell_vtx:4,cellcent:[11,12,17,18],celldimens:11,cellsiz:11,center:18,centers_to_nod:18,centertonod:18,certainli:3,cfd5:15,cfd:[8,15,16,23],cgn:[1,4,6,7,10,11,12,13,15,17,18,20,21,22],cgns_elmt_nam:20,cgns_io_tre:21,cgnsbase_t:[1,8,10,11,12,14,15,20],cgnscheck:15,cgnsconvert:15,cgnslibraryvers:12,cgnslibraryversion_t:12,cgnsname:[11,12,20],cgnsnode:[10,11,13,20],cgnstree:[10,11,12,13,14,18,20,21,22],cgnstree_t:[1,11,12],cgnsvalu:10,chain:[15,23],chang:[14,18,19,23],charact:[9,10],characterist:8,charg:9,check:[8,12,15],child:[10,12],children:[10,12,14],children_list:10,choic:[9,18],choos:[3,9,14,18],choosen:18,chosen:18,ci:7,circular:18,circumst:9,claim:9,clang:7,cleaner:7,clear:9,clone:7,close:8,closest:18,closestpoint:18,cloud:[17,18,20],cluster:[6,15],cmake:7,cmax:18,code:[3,4,11,12,13,14],coher:[4,7,8],collect:[1,8,18],color:[8,10,15],com:7,combin:9,come:[8,9,14,18],comfort:15,comm:[1,15,17,18,20,21,22],comm_world:[1,3,15,18,19,20,21,22],command:13,commerci:9,commit:13,common:[9,10,12,14],commonli:15,commun:[1,3,11,18,19,20,21,22],compact:10,compar:14,comparaison:11,comparison:[1,13],compat:[5,15,18,20],compil:[7,13,15,21],complet:[8,9],complex:10,complianc:9,compliant:[8,9,10,12,15],compon:18,comput:[8,9,15,16,18,20,21,23],compute_balanced_weight:20,compute_cell_cent:18,compute_edge_cent:18,compute_face_cent:[17,18],compute_nosplit_weight:20,compute_regular_weight:20,compute_wall_dist:[15,18],computegrad:15,concaten:1,concatenate_jn:18,concept:15,concern:9,concret:14,condit:[12,18,20],conf:[3,19],configur:[5,7,18],confirm:8,conflict:9,conform:[10,18],conformize_jn_pair:18,connect:[1,4,8,11,12,15,17,20],connect_1to1_famili:[17,18],connex:8,consecut:1,consequ:[10,12,20],consequenti:9,consid:[8,11,14,22],consist:[1,15,16,23],consol:3,constant:[1,8],constitut:9,constraint:[8,18],constru:9,construct:[6,13],consult:6,contain:[1,7,8,9,11,12,14,15,17,18,20,21,22],container_nam:18,containers_nam:[15,18],content:[9,13],context:[8,13],contigu:[8,10],continu:13,contract:9,contrari:8,contrast:8,contribut:[6,9,13,18],contributor:9,control:[9,18,20,21],convei:9,conveni:[3,20,22],convent:[1,2,3,15,18,21],convers:[12,17,23],convert:[10,12,14,15,18,21],convert_elements_to_mix:18,convert_elements_to_ngon:[1,17,18],convert_mixed_to_el:[17,18],convert_s_to_ngon:[15,18],convert_s_to_u:[17,18],coord:12,coordin:[8,10,11,17,18,20],coordinatei:[8,11,12,21],coordinatex:[8,10,11,12,15,18],coordinatez:21,copi:[9,17,20],copyright:9,corner:17,correct:[3,5,9,17,18],correspond:[8,10,11,12,18,20,22,23],cost:[1,9],could:[1,8,12,23],count:[1,8],counter:9,counterpart:14,court:9,cover:[9,14],cpost:15,cpp:7,cpp_cgn:[5,7],cpp_cgns_unit_test:5,crash:17,crazi:20,creat:[1,7,8,9,10,12,13,15,17,18,19,20,21,22],create_extractor_from_zsr:18,create_interpolator_from_part_tre:18,creation:[9,13],criteria:[13,14],cross:[9,18],cross_domain:18,ctest:5,ctransform:15,cumbersom:14,cur_base_nam:11,current:[8,12,14,17,20],curv:15,custom:[10,20],customiz:14,cut:8,cuthil:20,cx:18,cy:18,cz:18,d:18,dai:[6,9],daili:15,damag:9,data:[6,10,11,12,15,16,17,20,21,22,23],dataarrai:[12,21],dataarray_t:[10,12,14,15,18,22],databas:15,datai:22,datakind:10,datatyp:[10,12],datax:22,dataz:22,dcmake_install_prefix:7,deadlock:19,deal:[8,9,21],death:9,debug:[20,21],decid:[3,14],declar:[3,8],declaratori:9,decreas:18,dedic:[13,15],deep:14,def:3,defect:9,defend:9,defin:[6,8,9,10,11,12,15,18,20,22],definit:[8,12,22],delet:9,densiti:[12,22],depend:[1,8,10,11,13,14,15,18],depth:14,deriv:14,describ:[1,4,5,6,8,9,10,11,12,13,15,18,20,21,23],descript:[2,9,10,12,13,19,20,21],descriptor_t:12,design:12,desir:9,destin:23,destructur:[6,18],detail:[0,6,7,9,12,13,19],detain:8,detect:[15,18,21],determin:[1,11],dev:15,develop:[6,16],developp:[3,7],df:14,dic:18,dict:[12,18,20],dictionari:[20,22],dictionnari:[12,18,20],did:8,differ:[7,8,9,10,11,13,14,15,20,23],dimens:[8,11,12,18,20],dimension:[11,20],dir:7,direct:[9,11,20],directli:[8,9,14,19,23],directori:9,dirichletdata:22,disabl:[10,18,19],disable_mpi_excepthook:19,discretedata:20,discretedata_t:[18,22],disk:23,dispatch:[20,21],displai:[9,10,11],displaystyl:1,dispos:8,dist:[1,8,15,17,18,23],dist_to_full_tre:[17,20],dist_tre:[1,15,18,20,21,22],dist_tree_:18,dist_tree_bck:20,dist_tree_elt:1,dist_tree_gath:18,dist_tree_ini:18,dist_tree_src:18,dist_tree_tgt:18,dist_tree_to_fil:[15,21],dist_tree_to_part_tree_al:22,dist_tree_to_part_tree_only_label:22,dist_zon:[1,22],dist_zone_to_part_zones_al:22,dist_zone_to_part_zones_onli:22,distanc:[18,23],distinct:18,distinguis:1,distinguish:[8,9],distribut:[1,6,15,17,20,22,23],distribute_tre:17,distributor:9,disttre:[18,22],dive:8,divid:[1,20,21],divis:8,doabl:3,docstr:13,doctest:[7,13],doctrin:9,document:[7,9,13,18,20,21],doe:[3,7,8,9,10,18],domain:[8,18],domin:1,don:[7,12],done:[1,7,10,12,18],donor_nam:[11,12],donornam:17,dot:17,download:[7,15],doxygen:7,dr:5,drafter:9,driver:21,dsi:15,dtype:[10,11,12,18],dtypelik:12,due:[8,10],dump:20,dump_pdm_output:20,duplic:[15,18],duplicate_from_periodic_jn:15,duplicate_from_rotation_jns_to_360:18,dure:[1,5,7],dynam:[7,16,19],e:[1,5,7,8,11,18,19,22,23],each:[1,3,4,8,9,10,11,12,14,15,17,18,20,21,22,23],earlier:9,eas:5,easi:[13,14],easier:17,easiest:[19,21],easili:[13,15],ec:12,econn:12,edg:[4,8,12,17,18,20],edge_cent:18,edge_length:20,edge_vtx:4,edgecent:11,edit:13,effect:[19,20],effici:15,eg:12,either:[4,9,10,12,18,23],el8:15,el8_mpi:15,element:[1,4,8,11,12,15,17,18,20],element_t:[8,11,12,18],elementari:15,elementconnect:[1,8,12],elementrang:[8,11,12,18],elements_t:[8,11,12,18,20],elements_to_ngon:0,elementstartoffset:[1,12,15,18],elementtyp:18,elev:18,els:20,elsa:[15,16],elt:[4,11,17,18],elt_fac:4,elt_nod:11,elt_ordering_by_dim:11,elt_typ:18,elt_vtx:4,embed:[14,21],emploi:10,empti:[8,10,14,15,20],enabl:17,enclos:18,encompass:3,encount:8,end:[1,8,9,14,22],enforc:[9,12],enforce_ngon_pe_loc:18,englob:18,enough:3,enrich:23,ensur:18,entir:[8,9],entiti:[8,9,17,18,20],env:15,environ:[3,5,6,7,15,19],eq1:1,equal:[1,8,11,18,20,21],equat:[1,18],equilibr:20,equival:[3,8,9],erang:[11,12],error:[3,10,17,19],eso:12,essenti:9,etc:[7,8,20],evalu:10,even:[9,21,23],event:9,everi:[8,9,13,14,20,22],everyth:22,exactli:[1,11,18],exampl:[4,8,10,11,12,14,15,18,20,21,22,23],except:[1,9,10,18,22],excepthook:19,exchang:[1,8,18,22],exchange_field:18,exclud:[7,9,18,22],exclude_dict:22,exclus:9,exclusivelli:12,execut:[1,5,8,13,19],exercis:9,exist:[3,8,10,11,18,20,22,23],expect:[4,12,13,14,15,18,20,22],expected_:4,experi:13,experiment:[1,18],explain:[8,13,19],explicitli:[8,9,12,15,18],exploit:[9,13],explor:[13,14],expos:18,express:[9,20],extend:[18,20],extens:[3,6,16],extent:[9,11,12],exterior:1,extern:[1,5,7,13,18,20],extract:[6,11,12,15,17],extract_part_from_bc_nam:[15,18],extract_part_from_famili:[17,18],extract_part_from_xxx:17,extract_part_from_zsr:[17,18],extract_tre:15,extract_tree_dist:15,extracted_bc:18,extracted_tre:18,extractor:18,f:[1,10,14,15,18],face:[4,6,8,11,18,20],face_cel:4,face_cent:18,face_renum_method:20,face_vtx:4,facecent:[1,11,12,14,18,20],faces:11,facevtx:1,fact:[8,10],factor:18,factori:[15,17,18,21,22,23],factual:9,fail:[9,18],failur:9,fake:18,fals:[10,11,14,18,20,21],famili:[12,17,18],family_bc:12,family_nam:[12,18],family_t:[10,12,18],familybc:12,familybc_t:12,familynam:[12,17,18],familyname_t:[12,18],faster:1,featur:[13,14,15,18],fee:9,feedback:13,feel:3,feflo:[17,18],feflo_opt:18,few:[7,10,18,23],field:[8,11,12,15,17,18,20,23],fifti:9,figur:15,file:[5,7,8,9,10,15,17,19,23],file_print:[3,19],file_to_dist_tre:[1,15,17,18,20,21,22],file_to_part_tre:[17,21],filenam:[21,22],fill:[8,12,21],fill_size_tre:21,filter:[7,21,22],find:[1,18],find_closest_point:18,find_connected_zon:11,find_periodic_jn:11,fine:8,finer:21,first:[1,6,8,9,11,14,17,18],firstsolut:22,fit:9,flag:11,flat:[15,18],float32:[10,11],flow:12,flowsolut:[12,18,20,22],flowsolution_t:[8,12,15,17,18,22],fluid:16,folder:[3,5],follow:[1,7,8,9,10,11,12,13,14,15,18,19,20,21,22],footnot:8,forc:19,form:[8,10,14],formal:8,format:[13,15],former:15,found:[7,11,14,15,18,21],foundat:9,fr:16,fraction:20,framework:16,free:9,friendli:15,from:[1,3,7,8,9,10,11,12,13,14,15,17,18,21,22,23],from_label:14,from_nam:14,fs:12,fsol:18,fulfil:13,full:[8,14,18,19,21],full_onera_compat:18,full_to_dist_tre:[17,20],full_tre:20,fulli:[8,15],further:9,futur:18,g:[1,5,7,8,18,19,22,23],garante:11,gather:[1,8,11,17,18,21],gc1to1_t:11,gc:[11,12,17,18],gc_name:12,gc_node:11,gc_t:[8,11,12,18],gcc:7,gener:[5,6,9,11,16,17,19,21,23],generate_dist_block:[15,17,18,20,21],generate_dist_point:20,generate_dist_spher:[18,20],generate_jns_vertex_list:18,geodes:20,geometr:[8,15,20,22],get:[1,5,7,8,10,13,17,20],get_:14,get_all_cgnsbase_t:14,get_all_zone_t:[14,18,20,22],get_child_from_:14,get_child_from_label:[14,15,20],get_child_from_nam:[14,18],get_child_from_name_and_label:14,get_child_from_valu:14,get_children:10,get_children_from_:14,get_children_from_label:14,get_children_from_nam:14,get_children_from_name_and_label:14,get_children_from_valu:14,get_elt_range_per_dim:11,get_label:[10,14],get_nam:[10,11,14,18],get_nod:14,get_node_from_:14,get_node_from_label:[14,18,20],get_node_from_nam:[14,18,20],get_node_from_name_and_label:14,get_node_from_path:[14,22],get_node_from_pred:14,get_node_from_valu:14,get_nodes_from_:14,get_nodes_from_label:14,get_nodes_from_nam:[14,18],get_nodes_from_name_and_label:14,get_nodes_from_pred:14,get_nodes_from_valu:14,get_ordered_el:11,get_ordered_elements_per_dim:11,get_rank:[3,18,20,21],get_siz:20,get_valu:[10,14],get_value_kind:10,get_value_typ:10,getpatch:11,getter:10,git:[5,7],git_config_submodul:[5,7],github:[7,16],gitlab:[6,7,13,15],give:[8,17],given:[8,9,10,13,14,15,18,20],global:[1,3,8,17,19,22],globalnumb:[8,20,21],gnu:9,gnum:[18,20],go:15,goal:13,goe:8,good:8,goodwil:9,govern:9,gradient:15,grai:15,graph:20,graph_part_tool:[18,20],green:8,grid:[12,18,20],gridconnect:[11,17,18],gridconnectivity1to1:11,gridconnectivity1to1_t:[11,12,18],gridconnectivity_0_to_1:8,gridconnectivity_t:[11,12,17,18],gridconnectivityproperti:[12,18],gridconnectivityproperty_t:12,gridconnectivitytyp:12,gridconnectivitytype_t:12,gridcoordin:[10,11,12,18,20],gridcoordinates_t:[8,10,12],gridloc:[1,11,12,14,18,20],gridlocation_t:[12,14,15,18],group:[11,15],guarante:18,guid:21,guidelin:13,h5py:21,ha:[7,8,9,11,14,17,18,21],hand:[10,20],handl:3,happen:8,hard:4,has_nface_el:11,has_ngon_el:11,have:[1,7,8,9,10,11,14,15,17,18,19,20,22],have_isolated_fac:18,hdf5:[7,8,21],hdf:[15,21],he:3,heavi:8,heavyweight:1,held:9,help:15,helper:7,helpful:16,here:[1,5,7,10,11,14,15],herebi:9,hereof:9,hesit:15,hessmach:18,hessmachxx:18,hessmachyi:18,hessmachyz:18,heterogen:[8,20],hex:1,hexa:[1,8,18],hexa_8:[4,18,20],hide:12,hierarch:[10,12],hierarchi:10,high:[6,15,16,21,22],higher:[11,18,20],highest:21,hilbert:[18,20],hint:13,hold:[4,8,20],homogen:20,hood:18,hope:13,host:6,how:[5,6,8,9,10,11,13,19,21],howev:[7,8,9,20,22],hpc:20,hpp:3,html:7,http:[7,9,16],hybrid:20,hyperplan:20,i4:[10,12],i8:10,i:[1,8,9,10,11,18,20],i_rank:[18,20],icosahedr:20,id:[1,8,11,17,18,21],idea:10,ident:1,identifi:10,idw:18,idw_pow:18,ie:[12,18,20],ifacecent:11,ii:9,illustr:[8,14,15,21],imagin:8,impi:15,implement:[8,11,18,20,23],impli:[1,3,9],implicit:[8,15],imposs:9,inaccuraci:9,incident:9,includ:[1,3,8,9,11,14,15,18,20,22],include_dict:22,increas:18,incur:9,indemn:9,indemnifi:9,independ:[6,8,13,18,21],index:[4,8,12],indexarray_t:[11,12],indexdimens:[11,12],indexrange_t:[12,14],indic:[8,11,14,18,21],indirect:9,indirectli:9,indistinctli:18,individu:9,induc:8,info:[3,7,19],inform:[1,8,9,12,13,17,18,19,20,21],informat:7,infra:7,infring:9,init:[5,7,18],initi:[9,18,20],injuri:9,inplac:[17,18,22,23],input:[1,10,11,14,15,17,18,20,23],inria:18,insert:[1,21],insid:8,inspect:[10,13],instal:[13,15,18,20,21],install_dir:7,instanc:[3,7,8,12],instanci:[3,18],instead:[7,14],instruct:19,insur:15,int32:[10,11,18],integ:[8,14,15,20],integr:13,intel2120:15,intel:[7,15],intellectu:9,intend:9,interest:12,interfac:20,interlac:[11,18],intermedi:14,intern:[1,8,18],interoper:13,interpol:17,interpolate_from_part_tre:18,interpret:3,interv:[4,8],introduc:8,introduct:[6,15],intuit:8,invers:[18,21],invert:1,investig:1,involv:[1,15,18],io:[1,6,15,16,17,18,20,22],irrelev:20,irrespect:18,is1to1:11,is_bc:14,is_pr:14,is_same_tre:20,iso_field:18,iso_part_tre:18,iso_surfac:18,iso_v:18,isobarycent:18,isol:18,isosurf:18,isosurfac:[17,18],isotrop:18,isperiod:11,issu:[6,12,15,18],iter:14,iter_all_cgnsbase_t:14,iter_all_zone_t:[14,18],iter_children_from_label:14,iter_children_from_nam:14,iter_children_from_name_and_label:14,iter_children_from_valu:14,iter_nodes_from_:14,iter_nodes_from_label:14,iter_nodes_from_nam:14,iter_nodes_from_name_and_label:14,iter_nodes_from_pred:14,iter_nodes_from_valu:14,iter_valu:12,iterationvalu:12,ith:8,its:[1,3,7,8,9,10,13,15,18,20],itself:[3,8,11,14,21],j:[7,8],jfacecent:11,jn:18,jn_path:18,jn_paths_for_dupl:18,job:15,join:[11,18,22],joint:[1,5],jth:8,judgment:9,judici:9,jurisdict:9,just:[1,7,8,12,14],k:1,keep:[1,8,10,14,18,20,21],kei:[8,12,18,22],kept:1,keyword:[14,20],kfacecent:11,kind:[1,6,7,8,9,10,11,14,18,20,22,23],know:[8,12],knowledg:8,known:[9,20,22],kwarg:[10,14,18,20],label:[1,10,11,12,13,14,18,22],lambda:[10,14],languag:9,larg:[10,15,18,20],last:[7,14],lastli:22,later:9,latest:15,latter:3,law:9,layout:12,ld_library_path:[5,7],lead:[10,14,18,20],least:20,left:[1,20],legaci:[8,15,18,21],legal:9,len:[10,11,14,18,20],length:[8,20],less:[8,20],lesser:9,let:[1,8,14,21],letter:10,level:[5,6,8,11,12,14,15,16,18,20,21],lexicograph:[1,20],liabl:9,librari:[3,4,6,7,8,15,16,20,21],light:[13,19],lightweight:8,like:[3,7,8,9,19],limit:[8,14,18],line:[7,15],link:[8,12,17,21],list:[10,11,12,14,18,20,21,22],lista:18,listb:18,listen:3,liter:10,ln:8,ln_to_gn:8,load:[8,15,20,21,23],load_size_tre:21,loc:[11,12,14,18],loc_contain:18,loc_toler:18,local:[1,3,4,8,18],localize_point:18,locat:[7,8,9,12,14,15,17,18,20],location_t:14,locationandclosest:18,log:[1,2],logger:19,logging_conf_fil:[3,19],logo:9,longer:10,look:[1,3,8,9,14,15,19],loop:14,loss:9,lost:9,lot:14,low:[8,16,21],lower:[1,11,12,18],lowest:18,m:[10,20,22],machin:[7,13],made:[6,9,18],magic:22,mai:[6,7,8,9,18,21,22],maia:[1,5,7,10,11,12,13,14,15,17,18,19,20,21,22,23],maia_build_fold:7,maia_doctest_unit_test:5,maia_fold:[5,7],maia_poly_new_to_old:[15,18],maia_poly_old_to_new:[15,18],main:[6,12,21,23],maintain:[9,15],make:[7,8,9,11,12,13,18],malfunct:9,manag:[2,6,7,8,9,17,19,22],mandatori:[10,18],mani:20,manipul:[6,10,13],manner:[9,21],manual:[6,14,15],manuel:23,map:[8,10,11,12,13,15,18,22],mark:9,mask:10,match:[11,12,14,18],matcha:[15,18],matchb:[15,18],materi:[9,15],mathcal:1,mathtt:8,matrix:[11,12,18],matter:9,max:[11,18],max_coord:20,max_depth:10,maxd:14,maximum:9,mean:[1,4,7,8,9,11,14,15,18,20,22],memori:[1,8,17,18,19],merchant:9,merg:[15,17,18],merge_all_zones_from_famili:18,merge_connected_zon:[15,18],merge_strategi:17,merge_zon:[17,18],merge_zones_from_famili:18,merged_zon:18,mergedzon:18,mesh:[1,4,11,12,16,17,20,22],mesh_dir:[1,18,20],mesh_fil:15,messag:3,method:[3,8,9,17,18,20,22,23],metric:18,mid:16,min:11,mind:[14,18],minim:[13,15,18,20],miss:[12,18,20,22],mix:[1,8,14,15,17,18],mlog:3,modern:13,modif:9,modifi:[1,11,17,18,23],modul:[6,7,15,16,17,21,23],momentum:22,momentumi:22,momentumx:22,momentumz:22,more:[3,6,8,9,10,12,13,14,18,19,20,22],moreov:9,most:[3,6,8,11,14,15],motiv:13,move:18,move_field:18,mpart:20,mpi4pi:[1,7,15,18,20,21,22],mpi:[1,3,5,7,15,18,19,20,21,22],mpi_abort:19,mpi_comm:1,mpi_file_print:3,mpi_rank_0_stderr_print:[3,19],mpi_rank_0_stdout_print:[3,19],mpi_stderr_print:3,mpi_stdout_print:[3,19],mpicomm:[18,20,21,22],mpirun:5,mpl:9,msg:3,mt:10,much:[1,8,15],multidimension:10,multipl:[14,15,17,18],must:[7,8,9,10,12,14,15,18,20,21,22],my:3,my_app:3,my_fil:3,my_logg:3,my_print:3,my_them:3,mybas:8,myelement:11,myfamili:12,mynod:[8,10],mynodenam:10,myupdatednod:10,myzon:[8,10],n0:8,n1:15,n1xn2:15,n2:15,n:[1,8,10,12,14,18],n_:1,n_bnd_vtx:12,n_cell:[11,12,18],n_cell_in_sect:1,n_cell_per_cach:20,n_elem:11,n_face:11,n_face_of_cell_typ:1,n_face_per_pack:20,n_i:1,n_part:20,n_part_tot:20,n_proc:4,n_rank:[8,20],n_thing:4,n_vtx:[4,11,12,20],n_vtx_bnd:11,naca0012:18,name:[3,7,9,10,11,12,14,17,18,20,21],name_and_label:14,namespac:[11,13],nan:18,natur:23,navig:10,ncell:18,ndarrai:11,nearest:18,nearli:20,necessari:[9,14],necessarili:8,need:[1,3,7,8,12,14,18,20,22],neglig:9,neighbour:18,neither:10,nest:10,net:7,never:8,new_:12,new_baseiterativedata:12,new_bc:[11,12],new_cgnsbas:12,new_cgnstre:12,new_child:[10,18],new_dataarrai:[12,18],new_el:[11,12],new_famili:12,new_familynam:12,new_flowsolut:[12,18],new_gridconnect:[11,12],new_gridconnectivity1to1:[11,12],new_gridconnectivityproperti:[11,12],new_gridcoordin:[11,12],new_gridloc:12,new_indexarrai:12,new_indexrang:12,new_nfaceel:[11,12],new_ngonel:[11,12],new_nod:[10,12,18],new_zon:[11,12],new_zonebc:[11,12],new_zonegridconnect:12,new_zonesubregion:[11,12,18],next:[8,10,14,15,17,22],nface:[8,11,17,18,20],nface_n:[4,11,12,15,18,20],nface_to_p:18,nfaceel:[12,18],nfacenod:[11,18],ngon:[8,11,15,17,18,20],ngon_n:[11,12,15,18,20],ngonel:[12,20],ngonnod:11,ngons_to_el:18,nodal:18,node:[1,8,13,15,17,18,20,21,22],nodes_to_cent:[17,18],nodetocent:18,non:[8,9,11,12,15,20],none:[3,10,11,12,14,17,18,20,22],nor:10,norm:8,normal:[1,11],normal_axi:11,notabl:[5,8],notat:[12,16],note:[1,7,8,9,12,14,15,18,19,21],noth:[9,13],notic:[8,10,12],notifi:9,notwithstand:9,now:[1,3,8,15,18,21],np:[5,10,12,18],nth:14,number:[1,4,5,9,11,12,17,18,20],numpi:[10,12,18],nvtx:11,ny:8,o:1,object:[3,8,10,17,18],oblig:9,observ:15,obtain:[9,20],occur:[1,17,18,19],odd:18,off:1,offer:9,often:[5,7,8,14,15,23],old:[15,18],omit:12,onc:[1,7,10,14,15,18],one:[1,6,7,8,9,10,11,12,14,15,18,21,22],onera:[6,7,16,18],onera_spack_repo:7,ones:[1,5,7,8,12,18,20],ongo:9,onli:[1,4,8,9,10,11,12,14,18,20,21,22,23],only_uniform:20,open:[4,6,8,10],oper:[1,10,11,15,17,18,20,21,22,23],opposit:[11,12,15,18],opposite_jn:15,oppzon:11,option:[8,9,10,11,13,14,17,18,22],optionn:12,order:[1,5,6,8,9,15,18,20],ordinari:9,org:9,organ:5,organis:10,orient:[3,20],origin:[1,8,9,18,20],os:22,other:[5,8,9,10,15,18,20],otherbas:11,otherwis:[9,10,11,13,14,18,20,21],our:[8,13],out:[7,10,15],out_fs_nam:18,outlin:15,output:[3,10,11,13,18,19,20,21],output_path:18,outstand:9,over:[1,6,8,18,20,21],overrid:[7,19],overset:[11,12],overview:[6,14],own:[7,8,9,15,19,23],owner:20,ownership:9,p0:8,p1:8,p2:8,p:18,packag:[7,15],page:[6,10,12,17,19],pair:[11,14,18],paradigm:[7,8,20],paradigma:18,parallel:[6,13,15,17,18,20,21,23],parallel_sort:1,paramet:[10,11,12,14,17,18,20,21,22],parameter:5,parametr:[10,18],parent:[1,4,10,11,12,15,18,20],parentel:[1,4,12,18],parentelementsposit:[1,12],parentnod:10,parentposit:1,parmeti:[18,20],parse_yaml_cgn:[10,11,14],part:[6,8,9,15,17,18,20,23],part_interface_loc:20,part_tre:[15,18,20,21,22],part_tree_iso:18,part_tree_src:18,part_tree_tgt:18,part_tree_to_dist_tree_al:[15,22],part_tree_to_dist_tree_only_label:22,part_tree_to_fil:21,part_zon:[18,22],part_zones_to_dist_zone_al:22,part_zones_to_dist_zone_onli:22,parti:[7,9],partial:[1,8],particular:[1,3,7,9,14],partion:18,partit:[1,6,15,17,22,23],partition_dist_tre:[15,18,20,21,22],pass:[12,18],patch:[12,22],patent:9,path:[7,11,12,14,15,18,20,21,22],pattern:[1,10,14],pe:[12,20],pe_to_nfac:[15,18],peak:1,penalti:8,penta_6:20,pep8:13,pepo:12,per:[8,11,20],percent:9,perfect:[1,6],perform:[9,12,14,15,17,18,20,21],perio:[12,18],period:[11,12,17,18],periodic_t:12,periodic_valu:11,periodicvalu:11,permit:9,person:9,phy_dim:12,physic:[12,18,20],physicaldimens:11,pi:18,pick:22,pictur:15,piec:8,pip3:7,place:[1,9,14,18],plane:[15,18],plane_eq:18,plane_slic:[15,18],planeslic:15,pleasant:13,plu:10,pnode:10,point:[5,8,11,17,18,20],point_cloud:[15,18],point_list:[11,12,18],point_list_donor:12,point_rang:[11,12],point_range_donor:12,pointlist:[1,8,11,12,18],pointlistdonor:[12,18],pointrang:[8,11,12,14],pointrange_t:15,pointrangedonor:12,poli:[18,20,21],polici:[17,18],poly_new_to_old:[17,18],poly_old_to_new:[17,18],polyedr:18,polygon:[12,18],polyhedr:[15,18],polyhedra:20,poor:[17,20],popul:7,portabl:[13,16],portion:9,posit:[1,8,20],possibl:[8,9,10,13,14,15,17,20,22],post:[15,16],power:[9,18],pr:14,practic:[1,7,10,15],pre:[1,15,16,20],predefin:10,predic:14,prefer:[9,10,15],prefix:[3,11],present:[7,8,18],preserv:[8,15,18],preserve_orient:[18,20],preset:[10,13],pressur:[18,22],prevent:[9,12,17],previou:[1,8,10,14],previous:1,princip:9,print:[3,10,14],print_if:10,print_tre:[10,12],printer:19,prior:9,prism:[1,18],privileg:21,probe1:12,probe:12,proc:[4,8,18,20],process:[1,4,5,7,8,9,15,16,18,19,20,21],produc:[8,18,20,21,23],product:15,profit:9,program:3,progress:16,prohibit:9,project:[6,18],project_build_dir:5,project_src_dir:5,project_util:7,prompt:13,prone:10,prop:12,propag:18,properli:8,properti:[1,8,9],propos:13,prove:9,provid:[0,3,6,7,8,9,10,11,12,14,15,16,18,19,20,21,22,23],provis:9,provision:9,pt:[10,11,12,13,14,15,18,20,22],ptscotch:[18,20],publish:9,pure:3,purpl:8,purpos:[9,19,21],put:[3,8,9,11,15],px:8,pybind11:7,pycgn:15,pyra:[1,11,18],pyra_5:[11,12,18,20],pytest:[5,7],pytest_check:7,pytest_parallel:[7,17],python3:7,python:[3,4,6,7,8,10,13,15,16,19,21],pythonpath:[5,7],pytre:[6,10,11,12,14,15,18,20,21,22],quad:[1,8,11,18],quad_4:[8,11,18,20],quad_4_interior:1,qualiti:[9,13],quantiti:8,quarter_crown_square_8:22,quick:[6,18],quicker:10,quickselect:1,quit:7,r4:[10,12],r8:[10,12,18],r:[10,18],radian:18,radiu:20,rais:[10,11,19],rand:18,random:[18,20],rang:[7,11,12,18,20],rank:[1,8,19,20,21,22],rank_id:20,rather:[3,10],raw:[10,20],reach:10,read:[3,10,11,15,17,18,21],read_link:[17,21],read_tre:[18,20,21],readabl:12,readi:[7,15],rearang:18,rearrange_element_sect:18,reason:[8,9],receipt:9,receiv:[1,3,9,15,18,20],recip:7,recipi:9,recommend:15,reconstitut:18,reconstruct:[8,17],record:18,recov:17,recover_dist_tre:[15,20],red:8,redispatch:21,redistribut:18,redistribute_tre:[17,18],redo:11,reduc:[12,14,17,18],reduct:18,redund:8,refer:[3,8,9,13,18,20],referencest:17,reform:9,regener:20,region:11,regroup:13,regular:[1,8,18],reinstat:9,rel:[11,18],relat:[8,9,11,12,17,18,21,22],relax:8,releas:15,relev:[9,10,11,12,13,14,18,20,22],relevantbc:11,reli:[15,16],reliabl:13,remain:18,remedi:9,remot:1,remov:[7,9,10,17,18,21],remove_p:18,removenfac:18,removep:18,renam:[9,17,18,21],render:10,renumb:[8,20],repair:9,repart:18,repartit:8,replac:[1,10,14,18],replic:8,repo:7,report:[15,20],repositori:[5,6,7],repres:[8,9,15,18],represent:[10,13],reproduc:9,request:[12,18,20,21],requir:[1,7,8,9,18,22],rescal:18,resel:9,reserv:12,reshap:[12,18],resp:[18,22],respect:[3,8,9,11,15],restrict:[9,14,18],result:[1,8,9,18,23],retriev:[6,16,18],revers:20,rewrit:10,right:[1,9,20],risk:9,rk:3,rm_nodes_from_nam:[18,21],robust:17,room:15,root:[14,18],rotat:18,rotation_angl:[12,18],rotation_cent:[12,18],rotationangl:[11,12],rotationcent:[11,12],rotor:12,round:8,royalti:9,rtol:11,ruamel:7,rule:[12,22],run:[5,15,18],runtimeerror:[10,11],runtimewarn:[10,12],s:[6,7,8,9,14,15,17,18,20,21,22],s_twoblock:[15,18,20],said:8,sale:9,same:[1,7,8,10,12,14,15,18,20],sampl:[15,21],sample_mesh_dir:22,satisfi:14,sator:15,save:[15,23],scalar:[10,18,20],scale:[1,17,18],scale_mesh:[17,18],scatter:1,scenario:8,scientif:16,scratchm:15,script:[3,15,18],sdtout:19,search:[10,13,18],second:[8,11,14,18],secondsolut:22,section:[0,8,9,12,13,15,18,22],see:[1,7,8,9,10,12,14,15,18,19,20,21],seen:8,select:[3,14,17,18],self:[3,15],sell:9,semi:[4,8],sen:12,send:[1,15],sens:[8,11],sensit:10,separ:[9,14,21],seq:18,sequenc:[10,18],sequenti:[1,13,15,20,21],serv:13,servic:[9,16],set:[3,10,12,15,16,18,19,20],set_children:10,set_label:10,set_nam:[10,15],set_valu:[10,12],setter:10,setup:[13,15],sever:[3,7,8,11,14,15],sh:[5,7,15],shall:9,shallow:14,shape:[8,10,15,18,21],share:[8,9,17,18],shift:[1,18],shorcut:14,shortcut:[12,18],shorten:13,shorter:10,should:[1,3,7,9,10,11,12,13,14,15,18,20,21],show:[10,21],sid:[10,11,12,13,14,15,18,21],side1:18,side2:18,side:18,signatur:[10,14],similar:[1,18,22],similarli:[10,15,18],simpl:[10,15],simpli:[8,14,20],simultan:12,sin:15,sinc:[1,8,14,15],singl:[8,12,14,15,18,19,20,21],single_fil:21,singular:4,size:[1,4,8,10,11,12,14,15,17,18,20,21],size_tre:21,skeleton:8,skill:9,slice:[15,18],slice_tre:[15,18],small:[10,20,23],smaller:17,snake_cas:4,snippet:[10,13],so:[3,4,7,8,9,14],socl:15,softwar:[6,9,16,18],solut:[12,18],solver:[8,23],some:[0,6,7,8,9,10,11,12,15,17,18,19,20,21,23],someth:[1,8,14],sometim:8,sonic:15,sort:[1,8,11],sort_by_rank:1,sort_int_ext:20,sourc:[5,6,15,18,22,23],spack_root:7,spacki:7,span:8,spane:8,special:[9,18],specialsolut:22,specif:[9,10,11,13,14,20],specifi:[11,12,13,18,22],speedup:1,sphere:[18,20],sphere_eq:18,spheric:[17,18,20],spherical_slic:18,sphinx:7,spiro:15,split:[1,8,14,15,17,20],splitabl:8,spot:1,squar:8,src_dir:7,src_locat:18,src_sol:18,src_tree:18,stable_sort:[1,18],stage:1,standalon:15,standard:[1,6,8,10,11,12,13,15,16,17,18,20],start:[3,6,8,11,14,18,22],start_rang:18,stat:[3,19],state:10,statutori:9,std:7,std_e:[1,3,5,7],std_e_unit_test:5,stderr:[3,10,19],stderr_print:3,stdout:[3,10],stdout_print:3,step:[1,7,14,18,21],steward:9,still:[3,13,18],stop:[10,14],stoppag:9,storag:16,store:[1,8,11,12,18,21],str:[10,11,12,14,18,20,21,22],strategi:18,strict:20,stride:8,string:[3,10,14,18],structur:[4,8,10,11,12,15,17,18,20,21,22,23],sub:[8,12,19],subfil:21,subject:9,sublicens:9,submesh:[8,17,18],submodul:5,subregion:[8,20],subset:[8,11,14,17,18],subset_loc:18,subset_merg:18,subset_nod:11,substanc:9,substr:14,succe:11,successfulli:18,suffici:9,suffix:[4,14,18],suit:8,suitabl:[10,13],sum:[1,8,20],sum_:1,summar:19,summari:11,support:[9,11,17,18,20,21,22],suppos:[18,22],sure:7,surfac:[15,17,18,20],surviv:9,sy:[10,19],system:[7,16],sz:4,t:[7,12,18],t_:1,t_p:1,tabl:19,take:[1,8,10,12,14,17,18,22],taken:[10,11],target:[18,20,22],tata:22,tell:[7,12],temperatur:12,tensor:18,tensori:18,term:8,termin:[14,19],test:[3,4,13,14,15],test_util:[1,18,20,22],tet:[1,18],tetra:[1,11],tetra_4:[11,15,18,20],text:[9,10],textio:10,textrm:1,tgt_sol:18,tgt_tree:18,tgt_zone:18,than:[1,3,8,9,10,12,13,17,18],thank:[1,8,15,18],thei:[1,3,7,8,9,11,12,15,18,20,21,22,23],them:[1,3,7,8,10,18,21],theme:3,themselv:10,theoret:1,theori:9,therefor:8,thereof:9,theses:[10,15],thi:[0,1,3,4,5,6,7,8,9,10,11,12,13,14,15,17,18,19,20,21,22,23],thing:[4,14],third:[7,9],thoroughli:13,those:[1,8,9,18,20,22],thought:18,three:[1,10,20,23],through:[8,12,18,19,20,23],thu:[8,10,14,18,21,23],tild:18,time:[1,3,8,9,11,19],time_valu:12,timevalu:12,tl:5,tmp:7,tmp_user:15,to_cgns_tre:[11,14],to_nod:[10,14],to_str:10,tol:18,toler:[11,15,18],too:[8,20],tool:[15,20,23],top:[11,12],topolog:[15,20],tort:9,total:[8,11,20],total_s:8,track:[6,8],trade:1,trademark:9,transfer:[8,9,15,17,18,20,23],transfer_dataset:18,transfert_dataset:17,transform:[6,11,12,15],transform_affin:[17,18],translat:[11,12,18],transmit:20,transpar:7,treat:8,treatement:18,tree:[1,6,10,11,12,13,14,15,17,18,21,23],tree_:15,tree_u:15,tri:[1,11,13,18],tri_3:[11,18,20],tri_3_interior:1,trigger:10,triplet:11,tune:18,tupl:[11,14,18],turbulentdist:18,tutori:13,tutuz:22,twice:[1,3],two:[1,4,8,10,14,18,21,22,23],txt:10,type:[1,3,8,10,11,12,13,18,20],typic:[3,7,8,23],u4:10,u8:10,u:[10,17,18,20],u_atb_45:[15,18],u_naca0012_multizon:18,u_twoblock:15,uelt_m6w:[1,18],unabl:15,unbalanc:18,uncatch:19,unchang:18,under:[1,6,9,10,11,12,13,14,15,17,18],underli:[10,15,18],understand:[9,14],undesir:19,unenforc:9,unfil:8,unfortun:4,uniform:[18,20],union:8,uniqu:[1,18,21],unit:4,unless:[9,10,13],unlimit:14,unloc:18,unmatch:18,unmodifi:9,unpartit:8,unset:[10,14],unsign:[17,18],unstructur:[1,8,10,11,12,15,18,20,23],unstuctur:20,until:[3,8,9,18],unwant:[17,21],up:[1,3,15,18],updat:[5,7,10,18,23],update_child:10,update_nod:10,updatednodenam:10,us:[1,3,4,5,6,7,8,10,11,12,13,14,15,17,18,19,20,21,22,23],usag:[15,19],useful:[11,14],user:[3,6,7,9,10,12,13,15,18,19,20],userdefin:[11,12],userdefined_t:10,userdefineddata_t:10,usr:15,usual:[4,11],util:[1,3,7,15,18,20,22],v3:7,v5:15,v:[9,18],valid:[10,18],validli:9,valu:[8,10,11,12,14,17,18,20],valueerror:[10,11],vari:1,variabl:[3,4,8,19],variant:[1,14],variou:[6,13,14,18],vector:[11,18,20],vectori:18,verbos:[5,10],veri:1,versa:3,version:[7,8,12,15,17],vertex:[4,8,11,14,15,17,18,20],vertexboundarys:11,vertexs:11,vertic:[1,4,8,11,18,20],vice:3,view:[1,7,8],virtual:8,vol_rank:18,volum:[15,17,18],vtx:[4,11,18],vtx_renum_method:20,vtx_sol:18,w:10,wa:[1,8,9,20],wai:[7,8,19,21],wall:[10,12,15,18,23],wall_dist:17,walldist:[15,18],want:[3,5,6,7,8,9,12,14,15,21],warn:[3,10,12,18,19],we:[1,3,4,5,7,8,10,12,13,14,15,18,21,22],weight:[18,20],welcom:13,well:[8,20,21],were:[1,8,18],what:[3,8,10,14,17,21],when:[3,4,8,12,14,17,18,19,20,22],where:[1,3,7,8,9,10,14,18,20],whether:9,which:[1,5,6,8,9,10,11,12,13,14,15,16,18,19,20,21,22,23],white:15,who:[3,9,18,20],whole:14,whose:[8,18,23],why:[10,15],wide:9,wih:14,wildcard:[14,17,18,22],within:[8,9,11,15,18,20,21],without:[7,9,20],word:10,work:[7,13,15],workflow:[2,15,21,23],world:[9,15],wors:1,worst:8,worth:3,would:[1,3,8,9],wrap:[17,18],writ:8,write:[10,12,14,16,17,21],write_tre:21,written:[4,8,21],x4:10,x8:10,x:[8,10,15,18],x_0:18,xx:18,xy:18,xz:18,y:[8,18],y_0:18,yaml:[1,7,10,11,14,18,20,22],yet:[8,18,20],ym:21,you:[6,7,8,9,12,15,18,23],your:[7,9,13,15,18,19,21,23],yy:18,yz:18,z:18,z_0:18,zbc:12,zero:[12,18,20],zgc:[12,17],zm:21,zone0:8,zone1:[8,11,14],zone2:[11,14],zone3:[11,14],zone:[1,8,10,11,12,14,17,18,20,21],zone_nod:11,zone_path:[17,18],zone_t:[8,10,11,12,14,18,20,22],zone_to_part:20,zone_typ:20,zonebc:[11,14,20,22],zonebc_t:[12,14],zonedonorpath:11,zonegridconnect:[11,12,15,20],zonegridconnectivity_t:[11,12],zonenam:[11,18],zonesubregion:[11,12,18,20,22],zonesubregion_t:[8,11,12,18,22],zonetyp:[10,12],zonetype_t:[10,12],zsr:[11,12,17,18],zsr_name:18,zsr_node:11,zsrextent:11,zz:18},titles:["Algorithms description","elements_to_ngons","Developer Manual","Log management","Conventions","Development workflow","Welcome to Maia!","Installation","Introduction","License","Basic node editing","Node inspection","Node creation presets","The pytree module","Node searching","Quick start","Related projects","Release notes","Algo module","Configuration","Factory module","File management","Transfer module","User Manual"],titleterms:{"0":[9,17],"1":[9,17],"10":9,"2":[9,17],"2023":17,"2024":17,"3":[9,17],"4":9,"5":9,"6":9,"7":9,"8":9,"9":9,"cassiop\u00e9":16,"function":14,"new":[9,17],"public":9,A:9,The:13,With:9,addit:9,advanc:[14,17],algo:18,algorithm:[0,1,18],all:1,altern:1,api:[10,12,14,17],applic:[8,9],argument:1,avail:3,b:9,basic:10,build:7,calcul:18,cellfac:1,cgn:[8,16],chain:14,chang:17,code:9,complex:1,compli:9,comput:1,concept:8,condit:[9,14],configur:[3,19],connect:18,convent:[4,8],convers:18,core:8,creat:3,creation:[10,12],data:[8,18],date:9,definit:9,depend:7,descript:[0,1],design:1,detail:11,dev:17,develop:[2,5,7],disclaim:9,distribut:[8,9,18,21],divid:8,document:6,due:9,edit:10,effect:9,elements_to_ngon:1,environn:15,exampl:1,except:19,execut:9,exhibit:9,explan:1,extract:18,face:1,factori:20,fair:9,featur:17,field:22,file:[3,21],fine:14,fix:17,folder:7,form:9,from:20,full:20,gener:[1,12,14,18,20],geometr:18,geometri:18,grant:9,handl:19,highlight:15,improv:17,inabl:9,incompat:9,inspect:11,instal:7,interfac:18,interior:1,interpol:18,introduct:8,io:21,januari:17,juli:17,larger:9,launch:5,level:22,liabil:9,licens:9,limit:9,litig:9,log:[3,19],logger:3,mai:17,maia:[3,6,8],manag:[3,18,20,21],manual:[2,7,23],march:17,mesh:[8,15,18],method:11,miscellan:9,modifi:9,modul:[5,13,18,20,22],mozilla:9,mpi:8,name:[4,8],nface:1,ngon:1,node:[10,11,12,14],note:17,notic:9,number:[8,14],option:[7,20],other:[4,7],output:1,overview:[8,11,12],own:3,paradigm:16,parallel:[1,8],partit:[8,18,20,21],prefer:7,preset:12,printer:3,procedur:7,project:16,pytre:13,quick:15,raw:21,recov:20,refer:[10,12,14],regul:9,relat:16,releas:17,reorder:20,repartit:20,represent:9,resourc:15,respons:9,result:14,scope:9,search:14,secondari:9,section:1,sequenti:18,simplifi:1,sourc:[7,9],spack:7,special:14,specif:[3,8],start:15,statut:9,sub:5,submodul:7,subsequ:9,summari:[6,14],support:15,term:9,termin:9,test:5,through:7,tool:18,transfer:22,transform:[1,18],tree:[8,20,22],troubleshout:15,tune:14,tutori:14,us:9,user:[17,23],v1:17,version:[9,14],vocabulari:10,warranti:9,welcom:6,work:9,workflow:[5,7],your:3,zone:22}}) \ No newline at end of file diff --git a/docs/1.3/user_manual/algo.html b/docs/1.3/user_manual/algo.html new file mode 100644 index 00000000..1d9d5d5f --- /dev/null +++ b/docs/1.3/user_manual/algo.html @@ -0,0 +1,1767 @@ + + + + + + + + + + Algo module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Algo module

+

The maia.algo module provides various algorithms to be applied to one of the +two kind of trees defined by Maia:

+
    +
  • maia.algo.dist module contains some operations applying on distributed trees

  • +
  • maia.algo.part module contains some operations applying on partitioned trees

  • +
+

In addition, some algorithms can be applied indistinctly to distributed or partitioned trees. +These algorithms are accessible through the maia.algo module.

+

The maia.algo.seq module contains a few sequential utility algorithms.

+
+

Distributed algorithms

+

The following algorithms applies on maia distributed trees.

+
+

Connectivities conversions

+
+
+convert_s_to_u(dist_tree, connectivity, comm, subset_loc={})
+

Performs the destructuration of the input dist_tree.

+

Tree is modified in place: a NGON_n or HEXA_8 (not yet implemented) +connectivity is generated, and the following subset nodes are converted: +BC_t, BCDataSet_t and GridConnectivity1to1_t.

+
+

Note

+

Exists also as convert_s_to_ngon() with connectivity set to +NGON_n and subset_loc set to FaceCenter.

+
+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Structured tree

  • +
  • connectivity (str) – Type of elements used to describe the connectivity. +Admissible values are "NGON_n" and "HEXA" (not yet implemented).

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • subset_loc (dict, optional) – Expected output GridLocation for the following subset nodes: BC_t, GC_t. +For each label, output location can be a single location value, a list +of locations or None to preserve the input value. Defaults to None.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+maia.algo.dist.convert_s_to_u(dist_tree, 'NGON_n', MPI.COMM_WORLD)
+for zone in maia.pytree.get_all_Zone_t(dist_tree):
+  assert maia.pytree.Zone.Type(zone) == "Unstructured"
+
+
+
+ +
+
+convert_elements_to_ngon(dist_tree, comm, stable_sort=False)
+

Transform an element based connectivity into a polyedric (NGon based) +connectivity.

+

Tree is modified in place : standard element are removed from the zones +and the PointList are updated. If stable_sort is True, face based PointList +keep their original values.

+

Requirement : the Element_t nodes appearing in the distributed zones +must be ordered according to their dimension (either increasing or +decreasing). Tree made of Mixed elements are supported +(convert_mixed_to_elements() is called under the hood).

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by standard elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • stable_sort (bool, optional) – If True, 2D elements described in the +elements section keep their original id. Defaults to False.

  • +
+
+
+

Note that stable_sort is an experimental feature that brings the additional +constraints:

+
+
    +
  • 2D meshes are not supported;

  • +
  • 2D sections must have lower ElementRange than 3D sections.

  • +
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_ngon(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+ngons_to_elements(t, comm)
+

Transform a polyedric (NGon) based connectivity into a standard nodal +connectivity.

+

Tree is modified in place : Polyedric element are removed from the zones +and Pointlist (under the BC_t nodes) are updated.

+

Requirement : polygonal elements are supposed to describe only standard +elements (ie tris, quads, tets, pyras, prisms and hexas)

+

WARNING: this function has not been parallelized yet

+
+
Parameters
+
    +
  • disttree (CGNSTree) – Tree with connectivity described by NGons

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +
+
+convert_elements_to_mixed(dist_tree, comm)
+

Transform an element based connectivity into a mixed connectivity.

+

Tree is modified in place : standard elements are removed from the zones. +Note that the original ordering of elements is preserved.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by standard elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+convert_mixed_to_elements(dist_tree, comm)
+

Transform a mixed connectivity into an element based connectivity.

+

Tree is modified in place : mixed elements are removed from the zones +and the PointList are updated.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with connectivity described by mixed elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'Uelt_M6Wing.yaml', MPI.COMM_WORLD)
+maia.algo.dist.convert_elements_to_mixed(dist_tree, MPI.COMM_WORLD)
+maia.algo.dist.convert_mixed_to_elements(dist_tree, MPI.COMM_WORLD)
+
+
+
+ +
+
+rearrange_element_sections(dist_tree, comm)
+

Rearanges Elements_t sections such that for each zone, +sections are ordered in ascending dimensions order +and there is only one section by ElementType. +Sections are renamed based on their ElementType.

+

The tree is modified in place. +The Elements_t nodes are guaranteed to be ordered by ascending ElementRange.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Tree with an element-based connectivity

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_block(11, 'PYRA_5', MPI.COMM_WORLD)
+pyras = PT.get_node_from_name(dist_tree, 'PYRA_5.0')
+assert PT.Element.Range(pyras)[0] == 1 #Until now 3D elements are first
+
+maia.algo.dist.rearrange_element_sections(dist_tree, MPI.COMM_WORLD)
+tris = PT.get_node_from_name(dist_tree, 'TRI_3') #Now 2D elements are first
+assert PT.Element.Range(tris)[0] == 1
+
+
+
+ +
+
+generate_jns_vertex_list(dist_tree, comm, have_isolated_faces=False)
+

For each 1to1 FaceCenter matching join found in the distributed tree, +create a corresponding 1to1 Vertex matching join.

+

Input tree is modified inplace: Vertex GridConnectivity_t nodes +are stored under distinct containers named from the original ones, suffixed +with #Vtx. Similarly, vertex GC nodes uses the original name suffixed +with #Vtx.

+

Only unstructured-NGon based meshes are supported.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • have_isolated_faces (bool, optional) – Indicate if original joins includes +faces who does not share any edge with other external (join) faces. +If False, disable the special treatement needed by such faces (better performances, +but will fail if isolated faces were actually present). +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree_s = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+dist_tree = maia.algo.dist.convert_s_to_ngon(dist_tree_s, MPI.COMM_WORLD)
+
+maia.algo.dist.generate_jns_vertex_list(dist_tree, MPI.COMM_WORLD)
+assert len(maia.pytree.get_nodes_from_name(dist_tree, 'match*#Vtx')) == 2
+
+
+
+ +
+
+

Geometry transformations

+
+
+duplicate_from_rotation_jns_to_360(dist_tree, zone_paths, jn_paths_for_dupl, comm, conformize=False, apply_to_fields=False)
+

Reconstitute a circular mesh from an angular section of the geometry.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input distributed tree

  • +
  • zone_paths (list of str) – List of pathes (BaseName/ZoneName) of the connected zones to duplicate

  • +
  • jn_paths_for_dupl (pair of list of str) – (listA, listB) where listA (resp. list B) stores all the +pathes of the GridConnectivity nodes defining the first (resp. second) side of a periodic match.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • conformize (bool, optional) – If true, ensure that the generated interface vertices have exactly same +coordinates (see conformize_jn_pair()). Defaults to False.

  • +
  • apply_to_fields (bool, optional) – See maia.algo.transform_affine(). Defaults to False.

  • +
+
+
+
+ +
+
+merge_zones(tree, zone_paths, comm, output_path=None, subset_merge='name', concatenate_jns=True)
+

Merge the given zones into a single one.

+

Input tree is modified inplace : original zones will be removed from the tree and replaced +by the merged zone. Merged zone is added with name MergedZone under the first involved Base +except if output_path is not None : in this case, the provided path defines the base and zone name +of the merged block.

+

Subsets of the merged block can be reduced thanks to subset_merge parameter:

+
    +
  • None : no reduction occurs : all subset of all original zones remains on merged zone, with a +numbering suffix.

  • +
  • 'name' : Subset having the same name on the original zones (within a same label) produces +and unique subset on the output merged zone.

  • +
+

Only unstructured-NGon trees are supported, and interfaces between the zones +to merge must have a FaceCenter location.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • zone_paths (list of str) – List of path (BaseName/ZoneName) of the zones to merge. +Wildcard * are allowed in BaseName and/or ZoneName.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • output_path (str, optional) – Path of the output merged block. Defaults to None.

  • +
  • subset_merge (str, optional) – Merging strategy for the subsets. Defaults to ‘name’.

  • +
  • concatenate_jns (bool, optional) – if True, reduce the multiple 1to1 matching joins related +to the merged_zone to a single one. Defaults to True.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 3
+
+maia.algo.dist.merge_zones(dist_tree, ["BaseA/blk1", "BaseB/blk2"], MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 2
+
+
+
+ +
+
+merge_zones_from_family(tree, family_name, comm, **kwargs)
+

Merge the zones belonging to the given family into a single one.

+

See merge_zones() for full documentation.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • family_name (str) – Name of the family (read from FamilyName_t node) +used to select the zones.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • kwargs – any argument of merge_zones(), excepted output_path

  • +
+
+
+
+

See also

+

Function merge_all_zones_from_families(tree, comm, **kwargs) does +this operation for all the Family_t nodes of the input tree.

+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+
+# FamilyName are not included in the mesh
+for zone in PT.get_all_Zone_t(dist_tree):
+  PT.new_child(zone, 'FamilyName', 'FamilyName_t', 'Naca0012')  
+
+maia.algo.dist.merge_zones_from_family(dist_tree, 'Naca0012', MPI.COMM_WORLD)
+
+zones = PT.get_all_Zone_t(dist_tree)
+assert len(zones) == 1 and PT.get_name(zones[0]) == 'naca0012'
+
+
+
+ +
+
+merge_connected_zones(tree, comm, **kwargs)
+

Detect all the zones connected through 1to1 matching jns and merge them.

+

See merge_zones() for full documentation.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Input distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • kwargs – any argument of merge_zones(), excepted output_path

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+
+maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD)
+assert len(maia.pytree.get_all_Zone_t(dist_tree)) == 1
+
+
+
+ +
+
+conformize_jn_pair(dist_tree, jn_paths, comm)
+

Ensure that the vertices belonging to the two sides of a 1to1 GridConnectivity +have the same coordinates.

+

Matching join with Vertex or FaceCenter location are admitted. Coordinates +of vertices are made equal by computing the arithmetic mean of the two +values.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input tree

  • +
  • jn_pathes (list of str) – Pathes of the two matching GridConnectivity_t +nodes. Pathes must start from the root of the tree.

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +
+
+adapt_mesh_with_feflo(dist_tree, metric, comm, container_names=[], constraints=None, periodic=False, feflo_opts='')
+

Run a mesh adaptation step using Feflo.a software.

+
+

Important

+
    +
  • Feflo.a is an Inria software which must be installed by you and exposed in your $PATH.

  • +
  • This API is experimental. It may change in the future.

  • +
+
+

Input tree must be unstructured and have an element connectivity. +Boundary conditions other than Vertex located are managed.

+

Adapted mesh is returned as an independant distributed tree.

+

Setting the metric

+

Metric choice is available through the metric argument, which can take the following values:

+
    +
  • None : isotropic adaptation is performed

  • +
  • str : path (starting a Zone_t level) to a scalar or tensorial vertex located field:

    +
      +
    • If the path leads to a scalar field (e.g. FlowSolution/Pressure), a metric is computed by +Feflo from this field.

    • +
    • If the path leads to a tensorial field (e.g. FlowSolution/HessMach), we collect its 6 component (named +after CGNS tensor conventions) and pass it to +Feflo as a user-defined metric.

    • +
    +
    FlowSolution FlowSolution_t
    +├───GridLocation GridLocation_t "Vertex"
    +├───Pressure DataArray_t R8 (100,)
    +├───HessMachXX DataArray_t R8 (100,)
    +├───HessMachYY DataArray_t R8 (100,)
    +├───...
    +└───HessMachYZ DataArray_t R8 (100,)
    +
    +
    +
  • +
  • list of 6 str : each string must be a path to a vertex located field representing one component +of the user-defined metric tensor (expected order is XX, XY, XZ, YY, YZ, ZZ)

  • +
+

Periodic mesh adaptation

+

Periodic mesh adaptation is available by activating the periodic argument. Information from +periodic 1to1 GridConnectivity_t nodes in dist_tree will be used to perform mesh adaptation.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree to be adapted. Only U-Elements +single zone trees are managed.

  • +
  • metric (str or list) – Path(s) to metric fields (see above)

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • container_names (list of str) – Name of some Vertex located FlowSolution to project on the adapted mesh

  • +
  • constraints (list of str) – BC names of entities that must not be adapted (default to None)

  • +
  • periodic (boolean) – perform periodic mesh adaptation

  • +
  • feflo_opts (str) – Additional arguments passed to Feflo

  • +
+
+
Returns
+

CGNSTree – Adapted mesh (distributed)

+
+
+
+

Warning

+

Although this function interface is parallel, keep in mind that Feflo.a is a sequential tool. +Input tree is thus internally gathered to a single process, which can cause memory issues on large cases.

+
+

Example

+
import mpi4py.MPI as MPI
+import maia
+import maia.pytree as PT
+
+from maia.algo.dist import adapt_mesh_with_feflo
+
+dist_tree = maia.factory.generate_dist_block(5, 'TETRA_4', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+
+# > Create a metric field
+cx, cy, cz = PT.Zone.coordinates(zone)
+fields= {'metric' : (cx-0.5)**5+(cy-0.5)**5 - 1}
+PT.new_FlowSolution("FlowSolution", loc="Vertex", fields=fields, parent=zone)
+
+# > Adapt mesh according to scalar metric
+adpt_dist_tree = adapt_mesh_with_feflo(dist_tree,
+                                       "FlowSolution/metric",
+                                       MPI.COMM_WORLD,
+                                       container_names=["FlowSolution"],
+                                       feflo_opts="-c 100 -cmax 100 -p 4")
+
+
+
+ +
+
+

Interface tools

+
+
+connect_1to1_families(dist_tree, families, comm, periodic=None, **options)
+

Find the matching faces between cgns nodes belonging to the two provided families.

+

For each one of the two families, all the BC_t or GridConnectivity_t nodes related to the family +through a FamilyName/AdditionalFamilyName node will be included in the pairing process. +These subset must have a Vertex or FaceCenter GridLocation.

+

If the interface is periodic, the transformation from the first to the second family +entities must be specified using the periodic argument; a dictionnary with keys +'translation', 'rotation_center' and/or 'rotation_angle' (in radians) is expected. +Each key maps to a 3-sized numpy array, with missing keys defaulting zero vector.

+

Input tree is modified inplace : relevant GridConnectivity_t with PointList and PointListDonor +data are created. +If all the original elements are successfully paired, the original nodes are removed. Otherwise, +unmatched faces remains in their original node which is suffixed by ‘_unmatched’.

+

This function allows the additional optional parameters:

+
    +
  • location (default = ‘FaceCenter’) – Controls the output GridLocation of +the created interfaces. ‘FaceCenter’ or ‘Vertex’ are admitted.

  • +
  • tol (default = 1e-2) – Geometric tolerance used to pair two points. Note that for each vertex, this +tolerance is relative to the minimal distance to its neighbouring vertices.

  • +
+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Input distributed tree. Only U connectivities are managed.

  • +
  • families (tuple of str) – Name of the two families to connect.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • periodic (dic, optional) – Transformation from first to second family if the interface is periodic. +None otherwise. Defaults to None.

  • +
  • **options – Additional options

  • +
+
+
+

Example

+
from mpi4py import MPI
+from numpy  import array, pi
+import maia
+import maia.pytree as PT
+from maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+
+# Remove data that should be created
+PT.rm_nodes_from_name(dist_tree, 'PointListDonor')
+PT.rm_nodes_from_name(dist_tree, 'GridConnectivityProperty')
+
+# Create FamilyName on interface nodes
+PT.new_node('FamilyName', 'FamilyName_t', 'Side1', 
+        parent=PT.get_node_from_name(dist_tree, 'matchA'))
+PT.new_node('FamilyName', 'FamilyName_t', 'Side2', 
+        parent=PT.get_node_from_name(dist_tree, 'matchB'))
+
+maia.algo.dist.connect_1to1_families(dist_tree, ('Side1', 'Side2'), MPI.COMM_WORLD,
+        periodic={'rotation_angle' : array([-2*pi/45.,0.,0.])})
+
+assert len(PT.get_nodes_from_name(dist_tree, 'PointListDonor')) == 2
+
+
+
+ +
+
+

Data management

+
+
+redistribute_tree(dist_tree, policy, comm)
+

Redistribute the data of the input tree according to the choosen distribution policy.

+

Supported policies are:

+
    +
  • uniform : each data array is equally reparted over all the processes

  • +
  • gather.N : all the data are moved on process N

  • +
  • gather : shortcut for gather.0

  • +
+

In both case, tree structure remains unchanged on all the processes +(the tree is still a valid distributed tree). +Input is modified inplace.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • policy (str) – distribution policy (see above)

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree_ini = maia.factory.generate_dist_block(21, 'Poly', MPI.COMM_WORLD)
+dist_tree_gathered = maia.algo.dist.redistribute_tree(dist_tree_ini, \
+    'gather.0', MPI.COMM_WORLD)
+
+
+
+ +
+
+
+

Partitioned algorithms

+

The following algorithms applies on maia partitioned trees.

+
+

Geometric calculations

+
+
+compute_cell_center(zone)
+

Compute the cell centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node. +Centers are computed using a basic average over the vertices of the cells.

+
+
Parameters
+

zone (CGNSTree) – Partitionned CGNS Zone

+
+
Returns
+

array – Flat (interlaced) numpy array of cell centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  cell_center = maia.algo.part.compute_cell_center(zone)
+
+
+
+ +
+
+compute_face_center(zone)
+

Compute the face centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node.

+

Centers are computed using a basic average over the vertices of the faces.

+
+

Note

+

If zone is described with standard elements, centers will be computed for elements +explicitly defined in cgns tree.

+
+
+
Parameters
+

zone (CGNSTree) – Partitionned 2D or 3D U CGNS Zone

+
+
Returns
+

array – Flat (interlaced) numpy array of face centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  face_center = maia.algo.part.compute_face_center(zone)
+
+
+
+ +
+
+compute_edge_center(zone)
+

Compute the edge centers of a partitioned zone.

+

Input zone must have cartesian coordinates recorded under a unique +GridCoordinates node, and a unstructured standard elements connectivity.

+
+

Note

+

If zone is described with standard elements, centers will be computed for elements +explicitly defined in cgns tree.

+
+
+
Parameters
+

zone (CGNSTree) – Partitionned 2D or 3D U-elts CGNS Zone

+
+
Returns
+

array – Flat (interlaced) numpy array of edge centers

+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, "QUAD_4",  MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+for zone in maia.pytree.iter_all_Zone_t(part_tree):
+  edge_center = maia.algo.part.geometry.compute_edge_center(zone)
+
+
+
+ +
+
+compute_wall_distance(part_tree, comm, point_cloud='CellCenter', out_fs_name='WallDistance', **options)
+

Compute wall distances and add it in tree.

+

For each volumic point, compute the distance to the nearest face belonging to a BC of kind wall. +Computation can be done using “cloud” or “propagation” method.

+
+

Note

+

Propagation method requires ParaDiGMa access and is only available for unstructured cell centered +NGon connectivities grids. In addition, partitions must have been created from a single initial domain +with this method.

+
+

Tree is modified inplace: computed distance are added in a FlowSolution container whose +name can be specified with out_fs_name parameter.

+

The following optional parameters can be used to control the underlying method:

+
+
    +
  • method ({‘cloud’, ‘propagation’}): Choice of the geometric method. Defaults to 'cloud'.

  • +
  • perio (bool): Take into account periodic connectivities. Defaults to True. +Only available when method=cloud.

  • +
+
+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Input partitionned tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • point_cloud (str, optional) – Points to project on the surface. Can either be one of +“CellCenter” or “Vertex” (coordinates are retrieved from the mesh) or the name of a FlowSolution +node in which coordinates are stored. Defaults to CellCenter.

  • +
  • out_fs_name (str, optional) – Name of the output FlowSolution_t node storing wall distance data.

  • +
  • **options – Additional options related to geometric method (see above)

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD)
+assert maia.pytree.get_node_from_name(part_tree, "WallDistance") is not None
+
+
+
+ +
+
+localize_points(src_tree, tgt_tree, location, comm, **options)
+

Localize points between two partitioned trees.

+

For all the points of the target tree matching the given location, +search the cell of the source tree in which it is enclosed. +The result, i.e. the gnum & domain number of the source cell (or -1 if the point is not localized), +are stored in a DiscreteData_t container called “Localization” on the target zones.

+

Source tree must be unstructured and have a NGon connectivity.

+

Localization can be parametred thought the options kwargs:

+
    +
  • loc_tolerance (default = 1E-6) – Geometric tolerance for the method.

  • +
+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned. Only U-NGon connectivities are managed.

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned. Structured or U-NGon connectivities are managed.

  • +
  • location ({'CellCenter', 'Vertex'}) – Target points to localize

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • **options – Additional options related to location strategy

  • +
+
+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt):
+  maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3])
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+
+maia.algo.part.localize_points(part_tree_src, part_tree_tgt, 'CellCenter', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt):
+  loc_container = PT.get_child_from_name(tgt_zone, 'Localization')
+  assert PT.Subset.GridLocation(loc_container) == 'CellCenter'
+
+
+
+ +
+
+find_closest_points(src_tree, tgt_tree, location, comm)
+

Find the closest points between two partitioned trees.

+

For all points of the target tree matching the given location, +search the closest point of same location in the source tree. +The result, i.e. the gnum & domain number of the source point, are stored in a DiscreteData_t +container called “ClosestPoint” on the target zones. +The ids of source points refers to cells or vertices depending on the chosen location.

+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned

  • +
  • location ({'CellCenter', 'Vertex'}) – Entity to use to compute closest points

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+dist_tree_tgt = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(dist_tree_tgt):
+  maia.algo.transform_affine(tgt_zone, rotation_angle=[170*3.14/180,0,0], translation=[0,0,3])
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+
+maia.algo.part.find_closest_points(part_tree_src, part_tree_tgt, 'Vertex', comm)
+for tgt_zone in maia.pytree.get_all_Zone_t(part_tree_tgt):
+  loc_container = PT.get_child_from_name(tgt_zone, 'ClosestPoint')
+  assert PT.Subset.GridLocation(loc_container) == 'Vertex'
+
+
+
+ +
+
+

Mesh extractions

+
+
+iso_surface(part_tree, iso_field, comm, iso_val=0.0, containers_name=[], **options)
+

Create an isosurface from the provided field and value on the input partitioned tree.

+

Isosurface is returned as an independant (2d) partitioned CGNSTree.

+
+

Important

+
    +
  • Input tree must be unstructured and have a ngon connectivity.

  • +
  • Input tree must have been partitioned with preserve_orientation=True partitioning option.

  • +
  • Input field for isosurface computation must be located at vertices.

  • +
  • This function requires ParaDiGMa access.

  • +
+
+
+

Note

+
    +
  • Once created, additional fields can be exchanged from volumic tree to isosurface tree using +_exchange_field(part_tree, iso_part_tree, containers_name, comm).

  • +
  • If elt_type is set to ‘TRI_3’, boundaries from volumic mesh are extracted as edges on +the isosurface (GridConnectivity_t nodes become BC_t nodes) and FaceCenter fields are allowed to be exchanged.

  • +
+
+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree on which isosurf is computed. Only U-NGon +connectivities are managed.

  • +
  • iso_field (str) – Path (starting at Zone_t level) of the field to use to compute isosurface.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • iso_val (float, optional) – Value to use to compute isosurface. Defaults to 0.

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output isosurface tree.

  • +
  • **options – Options related to plane extraction.

  • +
+
+
Returns
+

CGNSTree – Surfacic tree (partitioned)

+
+
+

Isosurface can be controled thought the optional kwargs:

+
+
    +
  • elt_type (str) – Controls the shape of elements used to describe +the isosurface. Admissible values are TRI_3, QUAD_4, NGON_n. Defaults to TRI_3.

  • +
  • graph_part_tool (str) – Controls the isosurface partitioning tool. +Admissible values are hilbert, parmetis, ptscotch. +hilbert may produce unbalanced partitions for some configurations. Defaults to ptscotch.

  • +
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex')
+
+part_tree_iso = maia.algo.part.iso_surface(part_tree, "WallDistance/TurbulentDistance", iso_val=0.25,\
+    containers_name=['WallDistance'], comm=MPI.COMM_WORLD)
+
+assert maia.pytree.get_node_from_name(part_tree_iso, "WallDistance") is not None
+
+
+
+ +
+
+plane_slice(part_tree, plane_eq, comm, containers_name=[], **options)
+

Create a slice from the provided plane equation \(ax + by + cz - d = 0\) +on the input partitioned tree.

+

Slice is returned as an independant (2d) partitioned CGNSTree. See iso_surface() +for use restrictions and additional advices.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree to slice. Only U-NGon connectivities are managed.

  • +
  • sphere_eq (list of float) – List of 4 floats \([a,b,c,d]\) defining the plane equation.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output slice tree.

  • +
  • **options – Options related to plane extraction (see iso_surface()).

  • +
+
+
Returns
+

CGNSTree – Surfacic tree (partitioned)

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', MPI.COMM_WORLD)
+maia.algo.dist.merge_connected_zones(dist_tree, MPI.COMM_WORLD) # Isosurf requires single block mesh
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+slice_tree = maia.algo.part.plane_slice(part_tree, [0,0,1,0.5], MPI.COMM_WORLD, elt_type='QUAD_4')
+
+
+
+ +
+
+spherical_slice(part_tree, sphere_eq, comm, containers_name=[], **options)
+

Create a spherical slice from the provided equation +\((x-x_0)^2 + (y-y_0)^2 + (z-z_0)^2 = R^2\) +on the input partitioned tree.

+

Slice is returned as an independant (2d) partitioned CGNSTree. See iso_surface() +for use restrictions and additional advices.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree to slice. Only U-NGon connectivities are managed.

  • +
  • plane_eq (list of float) – List of 4 floats \([x_0, y_0, z_0, R]\) defining the sphere equation.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer +on the output slice tree.

  • +
  • **options – Options related to plane extraction (see iso_surface()).

  • +
+
+
Returns
+

CGNSTree – Surfacic tree (partitioned)

+
+
+

Example

+
from mpi4py import MPI
+import numpy
+import maia
+import maia.pytree as PT
+dist_tree = maia.factory.generate_dist_block(11, 'Poly', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD, preserve_orientation=True)
+
+# Add solution
+zone      = PT.get_node_from_label(part_tree, "Zone_t")
+vol_rank  = MPI.COMM_WORLD.Get_rank() * numpy.ones(PT.Zone.n_cell(zone))
+src_sol   = PT.new_FlowSolution('FlowSolution', loc='CellCenter', fields={'i_rank' : vol_rank}, parent=zone)
+
+slice_tree = maia.algo.part.spherical_slice(part_tree, [0.5,0.5,0.5,0.25], MPI.COMM_WORLD, \
+    ["FlowSolution"], elt_type="NGON_n")
+
+assert maia.pytree.get_node_from_name(slice_tree, "FlowSolution") is not None
+
+
+
+ +
+
+extract_part_from_zsr(part_tree, zsr_name, comm, transfer_dataset=True, containers_name=[], **options)
+

Extract the submesh defined by the provided ZoneSubRegion from the input volumic +partitioned tree.

+

Dimension of the output mesh is set up accordingly to the GridLocation of the ZoneSubRegion. +Submesh is returned as an independant partitioned CGNSTree and includes the relevant connectivities.

+

Fields found under the ZSR node are transfered to the extracted mesh if transfer_dataset is set to True. +In addition, additional containers specified in containers_name list are transfered to the extracted tree. +Containers to be transfered can be either of label FlowSolution_t or ZoneSubRegion_t.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree from which extraction is computed. U-Elts +connectivities are not managed.

  • +
  • zsr_name (str) – Name of the ZoneSubRegion_t node

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • transfer_dataset (bool) – Transfer (or not) fields stored in ZSR to the extracted mesh (default to True)

  • +
  • containers_name (list of str) – List of the names of the fields containers to transfer +on the output extracted tree.

  • +
  • **options – Options related to the extraction.

  • +
+
+
Returns
+

CGNSTree – Extracted submesh (partitioned)

+
+
+

Extraction can be controled by the optional kwargs:

+
+
    +
  • graph_part_tool (str) – Partitioning tool used to balance the extracted zones. +Admissible values are hilbert, parmetis, ptscotch. Note that +vertex-located extractions require hilbert partitioning. Defaults to hilbert.

  • +
+
+
+

Important

+
    +
  • Input tree must have a U-NGon or Structured connectivity

  • +
  • Partitions must come from a single initial domain on input tree.

  • +
+
+
+

See also

+

create_extractor_from_zsr() takes the same parameters, excepted containers_name, +and returns an Extractor object which can be used to exchange containers more than once through its +Extractor.exchange_fields(container_name) method.

+
+

Example

+
from   mpi4py import MPI
+import numpy as np
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex')
+
+# Create a ZoneSubRegion on procs for extracting odd cells
+for part_zone in PT.get_all_Zone_t(part_tree):
+  ncell       = PT.Zone.n_cell(part_zone)
+  start_range = PT.Element.Range(PT.Zone.NFaceNode(part_zone))[0]
+  point_list  = np.arange(start_range, start_range+ncell, 2, dtype=np.int32).reshape((1,-1), order='F')
+  PT.new_ZoneSubRegion(name='ZoneSubRegion', point_list=point_list, loc='CellCenter', parent=part_zone)
+
+extracted_tree = maia.algo.part.extract_part_from_zsr(part_tree, 'ZoneSubRegion', MPI.COMM_WORLD,
+                                                      containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_tree, "WallDistance") is not None
+
+
+
+ +
+
+extract_part_from_bc_name(part_tree, bc_name, comm, transfer_dataset=True, containers_name=[], **options)
+

Extract the submesh defined by the provided BC name from the input volumic +partitioned tree.

+

Behaviour and arguments of this function are similar to those of extract_part_from_zsr(): +zsr_name becomes bc_name and optional transfer_dataset argument allows to +transfer BCDataSet from BC to the extracted mesh (default to True).

+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex')
+
+extracted_bc = maia.algo.part.extract_part_from_bc_name(part_tree, \
+               'wall', MPI.COMM_WORLD, containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_bc, "WallDistance") is not None
+
+
+
+ +
+
+extract_part_from_family(part_tree, family_name, comm, transfer_dataset=True, containers_name=[], **options)
+

Extract the submesh defined by the provided family name from the input volumic +partitioned tree.

+

Family related nodes can be labelled either as BC_t or ZoneSubRegion_t, but their +GridLocation must have the same value. They generate a merged output on the resulting extracted tree.

+

Behaviour and arguments of this function are similar to those of extract_part_from_zsr().

+
+

Warning

+

Only U-NGon meshes are managed in this function.

+
+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_ATB_45.yaml', MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.algo.part.compute_wall_distance(part_tree, MPI.COMM_WORLD, point_cloud='Vertex')
+
+extracted_bc = maia.algo.part.extract_part_from_family(part_tree, \
+               'WALL', MPI.COMM_WORLD, containers_name=["WallDistance"])
+
+assert maia.pytree.get_node_from_name(extracted_bc, "WallDistance") is not None
+
+
+
+ +
+
+

Interpolations

+
+
+interpolate_from_part_trees(src_tree, tgt_tree, comm, containers_name, location, **options)
+

Interpolate fields between two partitionned trees.

+

For now, interpolation is limited to lowest order: target points take the value of the +closest point (or their englobing cell, depending of choosed options) in the source mesh. +Interpolation strategy can be controled thought the options kwargs:

+
    +
  • strategy (default = ‘Closest’) – control interpolation method

    +
      +
    • ‘Closest’ : Target points take the value of the closest source cell center.

    • +
    • ‘Location’ : Target points take the value of the cell in which they are located. +Unlocated points have take a NaN value.

    • +
    • ‘LocationAndClosest’ : Use ‘Location’ method and then ‘ClosestPoint’ method +for the unlocated points.

    • +
    +
  • +
  • loc_tolerance (default = 1E-6) – Geometric tolerance for Location method.

  • +
+
+

Important

+

If strategy is not ‘Closest’, source tree must have an unstructured-NGON +connectivity and CellCenter located fields.

+
+
+

See also

+

create_interpolator_from_part_trees() takes the same parameters (excepted containers_name, +which must be replaced by src_location), and returns an Interpolator object which can be used +to exchange containers more than once through its Interpolator.exchange_fields(container_name) method.

+
+
+
Parameters
+
    +
  • src_tree (CGNSTree) – Source tree, partitionned. Only U-NGon connectivities are managed.

  • +
  • tgt_tree (CGNSTree) – Target tree, partitionned. Structured or U-NGon connectivities are managed.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the source FlowSolution_t nodes to transfer.

  • +
  • location ({'CellCenter', 'Vertex'}) – Expected target location of the fields.

  • +
  • **options – Options related to interpolation strategy

  • +
+
+
+

Example

+
import mpi4py
+import numpy
+import maia
+import maia.pytree as PT
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree_src = maia.factory.generate_dist_block(11, 'Poly', comm)
+dist_tree_tgt = maia.factory.generate_dist_block(20, 'Poly', comm)
+part_tree_src = maia.factory.partition_dist_tree(dist_tree_src, comm)
+part_tree_tgt = maia.factory.partition_dist_tree(dist_tree_tgt, comm)
+# Create fake solution
+zone = maia.pytree.get_node_from_label(part_tree_src, "Zone_t")
+src_sol = maia.pytree.new_FlowSolution('FlowSolution', loc='CellCenter', parent=zone)
+PT.new_DataArray("Field", numpy.random.rand(PT.Zone.n_cell(zone)), parent=src_sol)
+
+maia.algo.part.interpolate_from_part_trees(part_tree_src, part_tree_tgt, comm,\
+    ['FlowSolution'], 'Vertex')
+tgt_sol = PT.get_node_from_name(part_tree_tgt, 'FlowSolution')
+assert tgt_sol is not None and PT.Subset.GridLocation(tgt_sol) == 'Vertex'
+
+
+
+ +
+
+centers_to_nodes(tree, comm, containers_name=[], **options)
+

Create Vertex located FlowSolution_t from CellCenter located FlowSolution_t.

+

Interpolation is based on Inverse Distance Weighting +(IDW) method: +each cell contributes to each of its vertices with a weight computed from the distance +between the cell isobarycenter and the vertice. The method can be tuned with +the following kwargs:

+
    +
  • idw_power (float, default = 1) – Power to which the cell-vertex distance is elevated.

  • +
  • cross_domain (bool, default = True) – If True, vertices located at domain +interfaces also receive data from the opposite domain cells. This parameter does not +apply to internal partitioning interfaces, which are always crossed.

  • +
+
+
Parameters
+
    +
  • tree (CGNSTree) – Partionned tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer.

  • +
  • **options – Options related to interpolation, see above.

  • +
+
+
+
+

See also

+

A CenterToNode object can be instanciated with the same parameters, excluding containers_name, +and then be used to move containers more than once with its +move_fields(container_name) method.

+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import mesh_dir
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'U_Naca0012_multizone.yaml', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Init a FlowSolution located at Cells
+for part in PT.get_all_Zone_t(part_tree):
+  cell_center = maia.algo.part.compute_cell_center(part)
+  fields = {'ccX': cell_center[0::3], 'ccY': cell_center[1::3], 'ccZ': cell_center[2::3]}
+  PT.new_FlowSolution('FSol', loc='CellCenter', fields=fields, parent=part)
+
+maia.algo.part.centers_to_nodes(part_tree, comm, ['FSol'])
+
+for part in PT.get_all_Zone_t(part_tree):
+  vtx_sol = PT.get_node_from_name(part, 'FSol#Vtx')
+  assert PT.Subset.GridLocation(vtx_sol) == 'Vertex'
+
+
+
+ +
+
+nodes_to_centers(tree, comm, containers_name=[], **options)
+

Create CellCenter located FlowSolution_t from Vertex located FlowSolution_t.

+

Interpolation is based on Inverse Distance Weighting +(IDW) method: +each vertex contributes to the cell value with a weight computed from the distance +between the cell isobarycenter and the vertice. The method can be tuned with +the following kwargs:

+
    +
  • idw_power (float, default = 1) – Power to which the cell-vertex distance is elevated.

  • +
+
+
Parameters
+
    +
  • tree (CGNSTree) – Partionned tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • containers_name (list of str) – List of the names of the FlowSolution_t nodes to transfer.

  • +
  • **options – Options related to interpolation, see above.

  • +
+
+
+
+

See also

+

A NodeToCenter object can be instanciated with the same parameters, excluding containers_name, +and then be used to move containers more than once with its +move_fields(container_name) method.

+
+

Example

+
import mpi4py
+import maia
+import maia.pytree as PT
+comm = mpi4py.MPI.COMM_WORLD
+
+dist_tree = maia.factory.generate_dist_sphere(3, 'TETRA_4', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+# Init a FlowSolution located at Nodes
+for part in PT.get_all_Zone_t(part_tree):
+  cx, cy, cz = PT.Zone.coordinates(part)
+  fields = {'cX': cx, 'cY': cy, 'cZ': cz}
+  PT.new_FlowSolution('FSol', loc='Vertex', fields=fields, parent=part)
+
+maia.algo.part.nodes_to_centers(part_tree, comm, ['FSol'])
+
+for part in PT.get_all_Zone_t(part_tree):
+  cell_sol = PT.get_node_from_name(part, 'FSol#Cell')
+  assert PT.Subset.GridLocation(cell_sol) == 'CellCenter'
+
+
+
+ +
+
+
+

Generic algorithms

+

The following algorithms applies on maia distributed or partitioned trees

+
+
+transform_affine(t, rotation_center=array([0., 0., 0.]), rotation_angle=array([0., 0., 0.]), translation=array([0., 0., 0.]), apply_to_fields=False)
+

Apply the affine transformation to the coordinates of the given zone.

+

Input zone(s) can be either structured or unstructured, but must have cartesian coordinates. +Transformation is defined by

+
+\[\tilde v = R \cdot (v - c) + c + t\]
+

where c, t are the rotation center and translation vectors and R is the rotation matrix. +Note that when the physical dimension of the mesh is set to 2, rotation_angle must +be a scalar float.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher.

  • +
  • rotation_center (array) – center coordinates of the rotation

  • +
  • rotation_angler (array) – angles of the rotation

  • +
  • translation (array) – translation vector components

  • +
  • apply_to_fields (bool, optional) – if True, apply the rotation vector to the vectorial fields found under +following nodes : FlowSolution_t, DiscreteData_t, ZoneSubRegion_t, BCDataset_t. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+zone = maia.pytree.get_all_Zone_t(dist_tree)[0]
+
+maia.algo.transform_affine(zone, translation=[3,0,0])
+
+
+
+ +
+
+scale_mesh(t, s=1.0)
+

Rescale the GridCoordinates of the input mesh.

+

Input zone(s) can be either structured or unstructured, but must have cartesian coordinates. +Transformation is defined by

+
+\[\tilde v = S \cdot v\]
+

where S is the scaling matrix. +Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher

  • +
  • s (float or array of float) – Scaling factor in each physical dimension. Scalars automatically +extend to uniform array.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+
+assert maia.pytree.get_node_from_name(dist_tree, 'CoordinateX')[1].max() <= 1.
+maia.algo.scale_mesh(dist_tree, [3.0, 2.0, 1.0])
+assert maia.pytree.get_node_from_name(dist_tree, 'CoordinateX')[1].max() <= 3.
+
+
+
+ +
+
+pe_to_nface(t, comm=None, removePE=False)
+

Create a NFace node from a NGon node with ParentElements.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Distributed or Partitioned tree (or sequences of) +starting at Zone_t level or higher.

  • +
  • comm (MPIComm) – MPI communicator, mandatory only for distributed zones

  • +
  • remove_PE (bool, optional) – If True, remove the ParentElements node. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+tree = maia.factory.generate_dist_block(6, 'Poly', MPI.COMM_WORLD)
+
+for zone in maia.pytree.get_all_Zone_t(tree):
+  maia.algo.pe_to_nface(zone, MPI.COMM_WORLD)
+  assert maia.pytree.get_child_from_name(zone, 'NFaceElements') is not None
+
+
+
+ +
+
+nface_to_pe(t, comm=None, removeNFace=False)
+

Create a ParentElements node in the NGon node from a NFace node.

+

Input tree is modified inplace.

+
+
Parameters
+
    +
  • t (CGNSTree(s)) – Distributed or Partitioned tree (or sequences of) +starting at Zone_t level or higher.

  • +
  • comm (MPIComm) – MPI communicator, mandatory only for distributed zones

  • +
  • removeNFace (bool, optional) – If True, remove the NFace node. +Defaults to False.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+tree = maia.factory.generate_dist_block(6, 'NFace_n', MPI.COMM_WORLD)
+
+maia.algo.nface_to_pe(tree, MPI.COMM_WORLD)
+assert maia.pytree.get_node_from_name(tree, 'ParentElements') is not None
+
+
+
+ +
+
+

Sequential algorithms

+

The following algorithms applies on regular (full) pytrees. Note that +these compatibility functions are also wrapped in the maia_poly_old_to_new +and maia_poly_new_to_old scripts, see Quick start section.

+
+
+poly_new_to_old(tree, full_onera_compatibility=True)
+

Transform a tree with polyhedral unstructured connectivity with new CGNS 4.x conventions to old CGNS 3.x conventions.

+

The tree is modified in place.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree described with new CGNS convention.

  • +
  • full_onera_compatibility (bool) – if True, shift NFace and ParentElements ids to begin at 1, irrespective of the NGon and NFace ElementRanges, and make the NFace connectivity unsigned

  • +
+
+
+

Example

+
import maia
+from   maia.utils.test_utils import mesh_dir
+
+tree = maia.io.read_tree(mesh_dir/'U_ATB_45.yaml')
+assert maia.pytree.get_node_from_name(tree, 'ElementStartOffset') is not None
+
+maia.algo.seq.poly_new_to_old(tree)
+assert maia.pytree.get_node_from_name(tree, 'ElementStartOffset') is None
+
+
+
+ +
+
+poly_old_to_new(tree)
+

Transform a tree with polyhedral unstructured connectivity with old CGNS 3.x conventions to new CGNS 4.x conventions.

+

The tree is modified in place.

+

This function accepts trees with old ONERA conventions where NFace and ParentElements ids begin at 1, irrespective of the NGon and NFace ElementRanges, and where the NFace connectivity is unsigned. The resulting tree has the correct CGNS/SIDS conventions.

+
+
Parameters
+

tree (CGNSTree) – Tree described with old CGNS convention.

+
+
+

Example

+
import maia
+from   maia.utils.test_utils import mesh_dir
+
+tree = maia.io.read_tree(mesh_dir/'U_ATB_45.yaml')
+maia.algo.seq.poly_new_to_old(tree)
+assert maia.pytree.get_node_from_name(tree, 'ElementStartOffset') is None
+
+maia.algo.seq.poly_old_to_new(tree)
+assert maia.pytree.get_node_from_name(tree, 'ElementStartOffset') is not None
+
+
+
+ +
+
+enforce_ngon_pe_local(t)
+

Shift the ParentElements values in order to make it start at 1, as requested by legacy tools.

+

The tree is modified in place.

+
+
Parameters
+

t (CGNSTree(s)) – Tree (or sequences of) starting at Zone_t level or higher.

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+tree = maia.factory.generate_dist_block(11, 'Poly', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(tree, 'Zone_t')
+n_cell = PT.Zone.n_cell(zone)
+
+assert PT.get_node_from_name(zone, 'ParentElements')[1].max() > n_cell
+maia.algo.seq.enforce_ngon_pe_local(tree)
+assert PT.get_node_from_name(zone, 'ParentElements')[1].max() <= n_cell
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/user_manual/config.html b/docs/1.3/user_manual/config.html new file mode 100644 index 00000000..b143f95c --- /dev/null +++ b/docs/1.3/user_manual/config.html @@ -0,0 +1,335 @@ + + + + + + + + + + Configuration — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Configuration

+
+

Logging

+

Maia provides informations to the user through the loggers summarized +in the following table:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +

Logger

Purpose

Default printer

maia

Light general info

mpi_rank_0_stdout_printer

maia-warnings

Warnings

mpi_rank_0_stdout_printer

maia-errors

Errors

mpi_rank_0_stdout_printer

maia-stats

More detailed timings +and memory usage

No output

+

The easiest way to change this default configuration is to +set the environment variable LOGGING_CONF_FILE to provide a file +(e.g. logging.conf) looking like this:

+
maia          : mpi_stdout_printer        # All ranks output to sdtout
+maia-warnings :                           # No output
+maia-errors   : mpi_rank_0_stderr_printer # Rank 0 output to stderr
+maia-stats    : file_printer("stats.log") # All ranks output in the file
+
+
+

See Log management for a full description of available +printers or for instructions on how to create your own printers. +This page also explain how to dynamically change printers +directly in your Python application.

+
+
+

Exception handling

+

Maia automatically override the sys.excepthook +function to call MPI_Abort when an uncatched exception occurs. +This allow us to terminate the MPI execution and to avoid some deadlocks +if an exception is raised by a single process. +Note that this call force the global COMM_WORLD communicator to abort which +can have undesired effects if sub communicators are used.

+

This behaviour can be disabled with a call to +maia.excepthook.disable_mpi_excepthook().

+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/user_manual/factory.html b/docs/1.3/user_manual/factory.html new file mode 100644 index 00000000..4b3ce3c4 --- /dev/null +++ b/docs/1.3/user_manual/factory.html @@ -0,0 +1,857 @@ + + + + + + + + + + Factory module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Factory module

+

The maia.factory module can be used to generate parallel CGNS trees. Those can +be obtained from an other kind of tree, or can be generated from user parameters.

+
+

Generation

+

Generation functions create a distributed tree from some parameters.

+
+
+generate_dist_points(n_vtx, zone_type, comm, origin=array([0., 0., 0.]), max_coords=array([1., 1., 1.]))
+

Generate a distributed mesh including only cartesian points.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +of the zone is controled by the zone_type parameter:

+
    +
  • "Structured" (or "S") produces a structured zone

  • +
  • "Unstructured" (or "U") produces an unstructured zone

  • +
+

In all cases, the created zone contains only the cartesian grid coordinates; no connectivities are created. +The physical dimension of the output +is set equal to the length of the origin parameter.

+
+
Parameters
+
    +
  • n_vtx (int or array of int) – Number of vertices in each direction. Scalars +automatically extend to uniform array.

  • +
  • zone_type (str) – requested kind of points cloud

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • max_coords (array, optional) – Coordinates of the higher point of the generated mesh. Defaults to ones vector.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_points(10, 'Unstructured', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Zone.n_vtx(zone) == 10**3
+
+
+
+ +
+
+generate_dist_block(n_vtx, cgns_elmt_name, comm, origin=array([0., 0., 0.]), edge_length=1.0)
+

Generate a distributed mesh with a cartesian topology.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +and cell dimension of the zone is controled by the cgns_elmt_name parameter:

+
    +
  • "Structured" (or "S") produces a structured zone,

  • +
  • "Poly" produces an unstructured 3d zone with a NGon+PE connectivity,

  • +
  • "NFACE_n" produces an unstructured 3d zone with a NFace+NGon connectivity,

  • +
  • "NGON_n" produces an unstructured 2d zone with faces described by a NGon +node (not yet implemented),

  • +
  • Other names must be in ["TRI_3", "QUAD_4", "TETRA_4", "PYRA_5", "PENTA_6", "HEXA_8"] +and produces an unstructured 2d or 3d zone with corresponding standard elements.

  • +
+

In all cases, the created zone contains the cartesian grid coordinates and the relevant number +of boundary conditions.

+

When creating 2 dimensional zones, the +physical dimension +is set equal to the length of the origin parameter.

+
+
Parameters
+
    +
  • n_vtx (int or array of int) – Number of vertices in each direction. Scalars +automatically extend to uniform array.

  • +
  • cgns_elmt_name (str) – requested kind of elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • edge_length (float, optional) – Edge size of the generated mesh. Defaults to 1.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_block([10,20,10], 'Structured', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Zone.Type(zone) == "Structured"
+
+dist_tree = maia.factory.generate_dist_block(10, 'Poly', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Element.CGNSName(PT.get_child_from_label(zone, 'Elements_t')) == 'NGON_n'
+
+dist_tree = maia.factory.generate_dist_block(10, 'TETRA_4', MPI.COMM_WORLD)
+zone = PT.get_node_from_label(dist_tree, 'Zone_t')
+assert PT.Element.CGNSName(PT.get_child_from_label(zone, 'Elements_t')) == 'TETRA_4'
+
+
+
+ +
+
+generate_dist_sphere(m, cgns_elmt_name, comm, origin=array([0., 0., 0.]), radius=1.0)
+

Generate a distributed mesh with a spherical topology.

+

Returns a distributed CGNSTree containing a single CGNSBase_t and +Zone_t. The kind +and cell dimension of the zone is controled by the cgns_elmt_name parameter:

+
    +
  • "NFACE_n" produces an unstructured 3d zone with a NFace+NGon connectivity,

  • +
  • "NGON_n" produces an unstructured 2d zone with a NGon+Bar connectivity,

  • +
  • Other names must be in ["TRI_3", "TETRA_4"] +and produces an unstructured 2d or 3d zone with corresponding standard elements.

  • +
+

In all cases, the created zone contains the grid coordinates and the relevant number +of boundary conditions.

+

Spherical meshes are +class I geodesic polyhedra +(icosahedral). Number of vertices on the external surface is equal to +\(10m^2+2\).

+
+
Parameters
+
    +
  • m (int) – Strict. positive integer who controls the number of vertices (see above)

  • +
  • cgns_elmt_name (str) – requested kind of elements

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • origin (array, optional) – Coordinates of the origin of the generated mesh. Defaults +to zero vector.

  • +
  • radius (float, optional) – Radius of the generated sphere. Defaults to 1.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+import maia.pytree as PT
+
+dist_tree = maia.factory.generate_dist_sphere(10, 'TRI_3', MPI.COMM_WORLD)
+assert PT.Element.CGNSName(PT.get_node_from_label(dist_tree, 'Elements_t')) == 'TRI_3'
+
+
+
+ +
+
+

Partitioning

+
+
+partition_dist_tree(dist_tree, comm, **kwargs)
+

Perform the partitioning operation: create a partitioned tree from the input distributed tree.

+

The input tree can be structured or unstuctured, but hybrid meshes are not yet supported.

+
+

Important

+

Geometric information (such as boundary conditions, zone subregion, etc.) are reported +on the partitioned tree; however, data fields (BCDataSet, FlowSolution, etc.) are not +transfered automatically. See maia.transfer module.

+
+

See reference documentation for the description of the keyword arguments.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • **kwargs – Partitioning options

  • +
+
+
Returns
+

CGNSTree – partitioned cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+comm = MPI.COMM_WORLD
+i_rank, n_rank = comm.Get_rank(), comm.Get_size()
+dist_tree  = maia.factory.generate_dist_block(10, 'Poly', comm)
+
+#Basic use
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+
+#Crazy partitioning where each proc get as many partitions as its rank
+n_part_tot = n_rank * (n_rank + 1) // 2
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm, \
+    zone_to_parts={'Base/zone' : [1./n_part_tot for i in range(i_rank+1)]})
+assert len(maia.pytree.get_all_Zone_t(part_tree)) == i_rank+1
+
+
+
+ +
+

Partitioning options

+

Partitioning can be customized with the following keywords arguments:

+
+
+graph_part_tool
+

Method used to split unstructured blocks. Irrelevent for structured blocks.

+
+
Admissible values
+
    +
  • parmetis, ptscotch : graph partitioning methods,

  • +
  • hilbert : geometric method (only for NGon connectivities),

  • +
  • gnum : cells are dispatched according to their absolute numbering.

  • +
+
+
Default value
+

parmetis, if installed; else ptscotch, if installed; hilbert otherwise.

+
+
+
+ +
+
+zone_to_parts
+

Control the number, size and repartition of partitions. See Repartition.

+
+
Default value
+

Computed such that partitioning is balanced using +maia.factory.partitioning.compute_balanced_weights().

+
+
+
+ +
+
+part_interface_loc
+

GridLocation for the created partitions interfaces. Pre-existing interface keep their original location.

+
+
Admissible values
+

FaceCenter, Vertex

+
+
Default value
+

FaceCenter for unstructured zones with NGon connectivities; Vertex otherwise.

+
+
+
+ +
+
+reordering
+

Dictionnary of reordering options, which are used to renumber the entities in each partitioned zone. See +corresponding documentation.

+
+ +
+
+preserve_orientation
+

If True, the created interface faces are not reversed and keep their original orientation. Consequently, +NGonElements can have a zero left parent and a non zero right parent. +Only relevant for U/NGon partitions.

+
+
Default value
+

False

+
+
+
+ +
+
+dump_pdm_output
+

If True, dump the raw arrays created by paradigm in a CGNSNode at (partitioned) zone level. For debug only.

+
+
Default value
+

False

+
+
+
+ +
+
+

Repartition

+

The number, size, and repartition (over the processes) of the created partitions is +controlled through the zone_to_parts keyword argument: each process must provide a +dictionary associating the path of every distributed zone to a list of floats. +For a given distributed zone, current process will receive as many partitions +as the length of the list (missing path or empty list means no partitions); the size of these partitions +corresponds to the values in the list (expressed in fraction of the input zone). +For a given distributed zone, the sum of all the fractions across all the processes must +be 1.

+

This dictionary can be created by hand; for convenience, Maia provides three functions in the +maia.factory.partitioning module to create this dictionary.

+
+
+compute_regular_weights(tree, comm, n_part=1)
+

Compute a basic zone_to_parts repartition.

+

Each process request n_part partitions on each original zone (n_part can differ +for each proc). +The weights of all the parts produced from a given zone are homogeneous +and equal to the number of cells in the zone divided by the total +number of partitions requested for this zone.

+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
  • n_part (int,optional) – Number of partitions to produce on each zone by the proc. +Defaults to 1.

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+zone_to_parts = mpart.compute_regular_weights(dist_tree, MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_size() == 2:
+  assert zone_to_parts == {'Base/Large': [0.5], 'Base/Small': [0.5]}
+
+
+
+ +
+
+compute_balanced_weights(tree, comm, only_uniform=False)
+

Compute a well balanced zone_to_parts repartition.

+

Each process request or not partitions with heterogeneous weight on each +original zone such that:

+
    +
  • the computational load is well balanced, ie the total number of +cells per process is nearly equal,

  • +
  • the number of splits within a given zone is minimized,

  • +
  • produced partitions are not too small.

  • +
+
+

Note

+

Heterogeneous weights are not managed by ptscotch. Use parmetis as graph_part_tool +for partitioning if repartition was computed with this function, or set optional +argument only_uniform to True.

+
+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
  • only_uniform (bool, optional) – If true, an alternative balancing method is used +in order to request homogeneous weights, but load balance is less equilibrated. +Default to False.

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+comm = MPI.COMM_WORLD
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', comm)
+
+zone_to_parts = mpart.compute_balanced_weights(dist_tree, comm)
+if comm.Get_size() == 2 and comm.Get_rank() == 0:
+  assert zone_to_parts == {'Base/Large': [0.375], 'Base/Small': [1.0]}
+if comm.Get_size() == 2 and comm.Get_rank() == 1:
+  assert zone_to_parts == {'Base/Large': [0.625]}
+
+
+
+ +
+
+compute_nosplit_weights(tree, comm)
+

Compute a zone_to_parts repartition without splitting the blocks.

+

The initial blocks will be simply distributed over the available processes, +minimizing the total number of cells affected to a proc. This leads to a poor load +balancing and possibly to procs having no partitions at all.

+
+
Parameters
+
    +
  • tree (CGNSTree) – (Minimal) distributed tree : only zone names and sizes are needed

  • +
  • comm (MPI.Comm) – MPI Communicator

  • +
+
+
Returns
+

dictzone_to_parts dictionnary expected by partition_dist_tree()

+
+
+

Example

+
from mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+from   maia.factory import partitioning as mpart
+
+dist_tree = maia.io.file_to_dist_tree(mesh_dir/'S_twoblocks.yaml', MPI.COMM_WORLD)
+
+zone_to_parts = mpart.compute_nosplit_weights(dist_tree, MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_size() == 2:
+  assert len(zone_to_parts) == 1
+
+
+
+ +
+
+

Reordering options

+

For unstructured zones, the reordering options are transmitted to ParaDiGM in order to +control the renumbering of mesh entities in the partitions.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Kwarg

Admissible values

Effect

Default

cell_renum_method

“NONE”, “RANDOM”, “HILBERT”, +“CUTHILL”, “CACHEBLOCKING”, +“CACHEBLOCKING2”, “HPC”

Renumbering method for the cells

NONE

face_renum_method

“NONE”, “RANDOM”, “LEXICOGRAPHIC”

Renumbering method for the faces

NONE

vtx_renum_method

“NONE”, “SORT_INT_EXT”

Renumbering method for the vertices

NONE

n_cell_per_cache

Integer >= 0

Specific to cacheblocking

0

n_face_per_pack

Integer >= 0

Specific to cacheblocking

0

graph_part_tool

“parmetis”, “ptscotch”, +“hyperplane”

Graph partitioning library to +use for renumbering

Same as partitioning tool

+
+
+

Recovering from partitions

+
+
+recover_dist_tree(part_tree, comm)
+

Regenerate a distributed tree from a partitioned tree.

+

The partitioned tree should have been created using Maia, or +must at least contains GlobalNumbering nodes as defined by Maia +(see Partitioned trees).

+

The following nodes are managed : GridCoordinates, Elements, ZoneBC, ZoneGridConnectivity +FlowSolution, DiscreteData and ZoneSubRegion.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned CGNS Tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from mpi4py import MPI
+import maia
+comm = MPI.COMM_WORLD
+
+dist_tree_bck  = maia.factory.generate_dist_block(5, 'TETRA_4', comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree_bck, comm)
+
+dist_tree = maia.factory.recover_dist_tree(part_tree, comm)
+assert maia.pytree.is_same_tree(dist_tree, dist_tree_bck)
+
+
+
+ +
+
+
+

Managing full trees

+

Almost no function in maia supports full (not distributed) CGNS trees, +but they can be useful for compatibility with sequential libraries.

+
+
+full_to_dist_tree(tree, comm, owner=None)
+

Generate a distributed tree from a standard (full) CGNS Tree.

+

Input tree can be defined on a single process (using owner = rank_id), +or a copy can be known by all the processes (using owner=None).

+

In both cases, output distributed tree will be equilibrated over all the processes.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Full (not distributed) tree.

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • owner (int, optional) – MPI rank holding the input tree. Defaults to None.

  • +
+
+
Returns
+

CGNSTree – distributed cgns tree

+
+
+

Example

+
from   mpi4py import MPI
+import maia
+from   maia.utils.test_utils import mesh_dir
+comm = MPI.COMM_WORLD
+
+if comm.Get_rank() == 0:
+  tree = maia.io.read_tree(mesh_dir/'S_twoblocks.yaml', comm)
+else:
+  tree = None
+dist_tree = maia.factory.full_to_dist_tree(tree, comm, owner=0)
+
+
+
+ +
+
+dist_to_full_tree(dist_tree, comm, target=0)
+

Generate a standard (full) CGNS Tree from a distributed tree.

+

The output tree can be used with sequential tools, but is no more compatible with +maia parallel algorithms.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed CGNS tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • target (int, optional) – MPI rank holding the output tree. Defaults to 0.

  • +
+
+
Returns
+

CGNSTree – Full (not distributed) tree or None

+
+
+

Example

+
from   mpi4py import MPI
+import maia
+comm = MPI.COMM_WORLD
+
+dist_tree = maia.factory.generate_dist_sphere(10, 'TRI_3', comm)
+full_tree = maia.factory.dist_to_full_tree(dist_tree, comm, target=0)
+
+if comm.Get_rank() == 0:
+  assert maia.pytree.get_node_from_name(full_tree, ":CGNS#Distribution") is None
+else:
+  assert full_tree is None
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/user_manual/io.html b/docs/1.3/user_manual/io.html new file mode 100644 index 00000000..93494ea8 --- /dev/null +++ b/docs/1.3/user_manual/io.html @@ -0,0 +1,520 @@ + + + + + + + + + + File management — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

File management

+

Maia supports HDF5/CGNS file reading and writing, +see related documention.

+

The IO functions are provided by the maia.io module. All the high level functions +accepts a legacy parameter used to control the low level CGNS-to-hdf driver:

+
    +
  • if legacy==False (default), hdf calls are performed by the python module +h5py.

  • +
  • if legacy==True, hdf calls are performed by +Cassiopee.Converter module.

  • +
+

The requested driver should be installed on your computer as well as the +hdf5 library compiled with parallel support.

+
+

Distributed IO

+

Distributed IO is the privileged way to deal with CGNS files within your maia workflows. +Files are loaded as distributed trees, and, inversely, distributed trees can be written +into a single CGNS file.

+

High level IO operations can be performed with the two following functions, which read +or write all data they found :

+
+
+file_to_dist_tree(filename, comm, legacy=False)
+

Distributed load of a CGNS file.

+
+
Parameters
+
    +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
Returns
+

CGNSTree – Distributed CGNS tree

+
+
+
+ +
+
+dist_tree_to_file(dist_tree, filename, comm, legacy=False)
+

Distributed write to a CGNS file.

+
+
Parameters
+
    +
  • dist_tree (CGNSTree) – Distributed tree to write

  • +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+
+ +

The example below shows how to uses these high level functions:

+
from mpi4py import MPI
+import maia
+
+# Generate a sample tree
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+# Write
+maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+# Read
+tree = maia.io.file_to_dist_tree("tree.cgns", MPI.COMM_WORLD)
+
+
+

Finer control of what is written or loaded can be achieved with the following steps:

+
    +
  • For a write operation, the easiest way to write only some nodes in +the file is to remove the unwanted nodes from the distributed tree.

  • +
  • For a read operation, the load has to be divided into the following steps:

    +
      +
    • Loading a size_tree: this tree has only the shape of the distributed data and +not the data itself.

    • +
    • Removing unwanted nodes in the size tree;

    • +
    • Fill the filtered tree from the file.

    • +
    +
  • +
+

The example below illustrate how to filter the written or loaded nodes:

+
from mpi4py import MPI
+import maia
+
+# Generate a sample tree
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+
+# Remove the nodes we do not want to write
+maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateZ') #This is a DataArray
+maia.pytree.rm_nodes_from_name(dist_tree, 'Zm*') #This is some BC nodes
+# Write
+maia.io.dist_tree_to_file(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+# Read
+from maia.io.cgns_io_tree import load_size_tree, fill_size_tree
+dist_tree = load_size_tree("tree.cgns", MPI.COMM_WORLD)
+#For now dist_tree only contains sizes : let's filter it
+maia.pytree.rm_nodes_from_name(dist_tree, 'CoordinateY') #This is a DataArray
+maia.pytree.rm_nodes_from_name(dist_tree, 'Ym*') #This is some BC nodes
+fill_size_tree(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+
+
+
+

Partitioned IO

+

In some cases, it may be useful to write or read a partitioned tree (keeping the +partitioned zones separated). This can be achieved using the following functions:

+
+
+part_tree_to_file(part_tree, filename, comm, single_file=False, legacy=False)
+

Gather the partitioned zones managed by all the processes and write it in a unique +hdf container.

+

If single_file is True, one file named filename storing all the partitioned +zones is written. Otherwise, hdf links are used to produce a main file filename +linking to additional subfiles.

+
+
Parameters
+
    +
  • part_tree (CGNSTree) – Partitioned tree

  • +
  • filename (str) – Path of the output file

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • single_file (bool) – Produce a unique file if True; use CGNS links otherwise.

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+
+maia.io.part_tree_to_file(part_tree, 'part_tree.cgns', MPI.COMM_WORLD)
+
+
+
+ +
+
+file_to_part_tree(filename, comm, redispatch=False, legacy=False)
+

Read the partitioned zones from a hdf container and affect them +to the ranks.

+

If redispatch == False, the CGNS zones are affected to the +rank indicated in their name. +The size of the MPI communicator must thus be equal to the highest id +appearing in partitioned zone names.

+

If redispatch == True, the CGNS zones are dispatched over the +available processes, and renamed to follow maia’s conventions.

+
+
Parameters
+
    +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
  • redispatch (bool) – Controls the affectation of the partitions to the available ranks (see above). +Defaults to False.

  • +
+
+
Returns
+

CGNSTree – Partitioned CGNS tree

+
+
+
+ +
+
+

Raw IO

+

For debug purpose, trees can also be read or written independently in a +sequential manner. Be aware that information added by maia such as Distribution +or GlobalNumbering nodes will not be removed.

+
+
+read_tree(filename, legacy=False)
+

Sequential load of a CGNS file.

+
+
Parameters
+

filename (str) – Path of the file

+
+
Returns
+

CGNSTree – Full (not distributed) CGNS tree

+
+
+
+ +
+ +

Detect the links embedded in a CGNS file.

+

Links information are returned as described in sids-to-python. Note that +no data are loaded and the tree structure is not even built.

+
+
Parameters
+

filename (str) – Path of the file

+
+
Returns
+

list – Links description

+
+
+
+ +
+
+write_tree(tree, filename, links=[], legacy=False)
+

Sequential write to a CGNS file.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree to write

  • +
  • filename (str) – Path of the file

  • +
  • links (list) – List of links to create (see SIDS-to-Python guide)

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+if MPI.COMM_WORLD.Get_rank() == 0:
+  maia.io.write_tree(dist_tree, "tree.cgns")
+
+
+
+ +
+
+write_trees(tree, filename, comm, legacy=False)
+

Sequential write to CGNS files.

+

Write separate trees for each process. Rank id will be automatically +inserted in the filename.

+
+
Parameters
+
    +
  • tree (CGNSTree) – Tree to write

  • +
  • filename (str) – Path of the file

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+

Example

+
from mpi4py import MPI
+import maia
+
+dist_tree = maia.factory.generate_dist_block(10, "Poly", MPI.COMM_WORLD)
+maia.io.write_trees(dist_tree, "tree.cgns", MPI.COMM_WORLD)
+
+
+
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/user_manual/transfer.html b/docs/1.3/user_manual/transfer.html new file mode 100644 index 00000000..590c72fa --- /dev/null +++ b/docs/1.3/user_manual/transfer.html @@ -0,0 +1,498 @@ + + + + + + + + + + Transfer module — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

Transfer module

+

The maia.transfer contains functions that exchange data between the +partitioned and distributed meshes.

+
+

Fields transfer

+

High level APIs allow to exchange data at CGNS Tree or Zone level. +All the provided functions take similar parameters: a distributed tree (resp. zone), +the corresponding partitioned tree (resp. list of zones), the MPI communicator and +optionally a filtering parameter.

+

The following kind of data are supported: +FlowSolution_t, DiscreteData_t, ZoneSubRegion_t and BCDataSet_t.

+

When transferring from distributed meshes to partitioned meshes, fields are supposed +to be known on the source mesh across all the ranks (according to disttree definition). +Geometric patches (such as ZoneSubRegion or BCDataSet) must exists on the relevant partitions, meaning +that only fields are transfered.

+

When transferring from partitioned meshes to distributed meshes, geometric patches may or may not exist on the +distributed zone: they will be created if needed. This allows to transfer fields that have been created +on the partitioned meshes. +It is however assumed that global data (e.g. FlowSolution) are defined on every partition.

+
+

Tree level

+

All the functions of this section operate inplace and require the following parameters:

+
    +
  • dist_tree (CGNSTree) – Distributed CGNS Tree

  • +
  • part_tree (CGNSTree) – Corresponding partitioned CGNS Tree

  • +
  • comm (MPIComm) – MPI communicator

  • +
+
+
+dist_tree_to_part_tree_all(dist_tree, part_tree, comm)
+

Transfer all the data fields from a distributed tree +to the corresponding partitioned tree.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+dist_tree = maia.io.file_to_dist_tree(filename, MPI.COMM_WORLD)
+part_tree = maia.factory.partition_dist_tree(dist_tree, MPI.COMM_WORLD)
+maia.transfer.dist_tree_to_part_tree_all(dist_tree, part_tree, MPI.COMM_WORLD)
+
+zone = PT.get_all_Zone_t(part_tree)[0]
+assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_tree_to_dist_tree_all(dist_tree, part_tree, comm)
+

Transfer all the data fields from a partitioned tree +to the corresponding distributed tree.

+
+ +

In addition, the next two methods expect the parameter labels (list of str) +which allow to pick one or more kind of data to transfer from the supported labels.

+
+
+dist_tree_to_part_tree_only_labels(dist_tree, part_tree, labels, comm)
+

Transfer all the data fields of the specified labels from a distributed tree +to the corresponding partitioned tree.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['FlowSolution_t', 'ZoneSubRegion_t'], comm)
+
+zone = PT.get_all_Zone_t(part_tree)[0]
+assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None
+
+maia.transfer.dist_tree_to_part_tree_only_labels(dist_tree, part_tree, ['BCDataSet_t'], comm)
+assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_tree_to_dist_tree_only_labels(dist_tree, part_tree, labels, comm)
+

Transfer all the data fields of the specified labels from a partitioned tree +to the corresponding distributed tree.

+
+ +
+
+

Zone level

+

All the functions of this section operate inplace and require the following parameters:

+
    +
  • dist_zone (CGNSTree) – Distributed CGNS Zone

  • +
  • part_zones (list of CGNSTree) – Corresponding partitioned CGNS Zones

  • +
  • comm (MPIComm) – MPI communicator

  • +
+

In addition, filtering is possible with the use of the +include_dict or exclude_dict dictionaries. These dictionaries map +each supported label to a list of cgns paths to include (or exclude). Paths +starts from the Zone_t node and ends at the targeted DataArray_t node. +Wildcard * are allowed in paths : for example, considering the following tree +structure,

+
Zone (Zone_t)
+├── FirstSolution (FlowSolution_t)
+│   ├── Pressure (DataArray_t)
+│   ├── MomentumX (DataArray_t)
+│   └── MomentumY (DataArray_t)
+├── SecondSolution (FlowSolution_t)
+│   ├── Pressure (DataArray_t)
+│   ├── MomentumX (DataArray_t)
+│   └── MomentumY (DataArray_t)
+└── SpecialSolution (FlowSolution_t)
+    ├── Density (DataArray_t)
+    └── MomentumZ (DataArray_t)
+
+
+
+
"FirstSolution/Momentum*" maps to ["FirstSolution/MomentumX", "FirstSolution/MomentumY"],
+
"*/Pressure maps to ["FirstSolution/Pressure", "SecondSolution/Pressure"], and
+
"S*/M*" maps to ["SecondSolution/MomentumX", "SecondSolution/MomentumY", "SpecialSolution/MomentumZ"].
+
+

For convenience, we also provide the magic path ['*'] meaning “everything related to this +label”.

+

Lastly, we use the following rules to manage missing label keys in dictionaries:

+
+
    +
  • For _only functions, we do not transfer any field related to the missing labels;

  • +
  • For _all functions, we do transfer all the fields related to the missing labels.

  • +
+
+
+
+dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict)
+

Transfer the data fields specified in include_dict from a distributed zone +to the corresponding partitioned zones.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+dist_zone = PT.get_all_Zone_t(dist_tree)[0]
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+part_zones = PT.get_all_Zone_t(part_tree)
+
+include_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'],
+                'BCDataSet_t'    : ['*']}
+maia.transfer.dist_zone_to_part_zones_only(dist_zone, part_zones, comm, include_dict)
+
+for zone in part_zones:
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is not None
+  assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is     None
+  assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is not None
+
+
+
+ +
+
+part_zones_to_dist_zone_only(dist_zone, part_zones, comm, include_dict)
+

Transfer the data fields specified in include_dict from the partitioned zones +to the corresponding distributed zone.

+
+ +
+
+dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict={})
+

Transfer all the data fields, excepted those specified in exclude_dict, +from a distributed zone to the corresponding partitioned zones.

+

Example

+
from mpi4py import MPI
+import os
+import maia
+import maia.pytree as PT
+from   maia.utils.test_utils import sample_mesh_dir
+
+comm = MPI.COMM_WORLD
+filename = os.path.join(sample_mesh_dir, 'quarter_crown_square_8.yaml')
+
+dist_tree = maia.io.file_to_dist_tree(filename, comm)
+dist_zone = PT.get_all_Zone_t(dist_tree)[0]
+part_tree = maia.factory.partition_dist_tree(dist_tree, comm)
+part_zones = PT.get_all_Zone_t(part_tree)
+
+exclude_dict = {'FlowSolution_t' : ['FlowSolution/DataX', 'FlowSolution/DataZ'],
+                'BCDataSet_t'    : ['*']}
+maia.transfer.dist_zone_to_part_zones_all(dist_zone, part_zones, comm, exclude_dict)
+
+for zone in part_zones:
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataX') is     None
+  assert PT.get_node_from_path(zone, 'FlowSolution/DataY') is not None
+  assert PT.get_node_from_path(zone, 'ZoneSubRegion/Tata') is not None
+  assert PT.get_node_from_path(zone, 'ZoneBC/Bnd2/BCDataSet/DirichletData/TutuZ') is None
+
+
+
+ +
+
+part_zones_to_dist_zone_all(dist_zone, part_zones, comm, exclude_dict={})
+

Transfer all the data fields, excepted those specified in exclude_dict, +from the partitioned zone to the corresponding distributed zone.

+
+ +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/1.3/user_manual/user_manual.html b/docs/1.3/user_manual/user_manual.html new file mode 100644 index 00000000..16b263a0 --- /dev/null +++ b/docs/1.3/user_manual/user_manual.html @@ -0,0 +1,298 @@ + + + + + + + + + + User Manual — Maia documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +
+

User Manual

+

Maia methods are accessible through three main modules :

+
    +
  • Factory allows to generate Maia trees, generally from another kind of tree +(e.g. the partitioning operation). Factory functions return a new tree +whose nature generally differs from the input tree.

  • +
  • Algo is the main Maia module and provides parallel algorithms to be applied on Maia trees. +Some algorithms are only available for distributed trees +and some are only available for partitioned trees. +A few algorithms are implemented for both kind +of trees and are thus directly accessible through the algo module.

    +

    Algo functions either modify their input tree inplace, or return some data, but they do not change the nature +of the tree.

    +
  • +
  • Transfer is a small module allowing to transfer data between Maia trees. A transfer function operates +on two existing trees and enriches the destination tree with data fields of the source tree.

  • +
+

Using Maia trees in your application often consists in chaining functions from these different modules.

+../_images/workflow.svg

A typical workflow could be:

+
    +
  1. Load a structured tree from a file, which produces a dist tree.

  2. +
  3. Apply some distributed algorithms to this tree: for example a structured to unstructured conversion (algo.dist module).

  4. +
  5. Generate a corresponding partitionned tree (factory module).

  6. +
  7. Apply some partitioned algorithms to the part tree, such as wall distance computation (algo.part module), +and even call you own tools (e.g. a CFD solver)

  8. +
  9. Transfer the resulting fields to the dist tree (transfer module).

  10. +
  11. Save the updated dist tree to disk.

  12. +
+

This user manuel describes the main functions available in each module.

+
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + +
+ + Read the Docs + v: 1.3 + + +
+ + +
+
Versions
+ + +
1.3
+ +
1.2
+ +
1.1
+ +
1.0
+ +
+ + + +
+ Free document hosting provided by Read the Docs. + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..ce34f36b --- /dev/null +++ b/docs/index.html @@ -0,0 +1,3 @@ + + + diff --git a/external/cpp_cgns b/external/cpp_cgns deleted file mode 160000 index a861c34d..00000000 --- a/external/cpp_cgns +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a861c34d93690c5eefdb8051eb79010a24485da1 diff --git a/external/paradigm b/external/paradigm deleted file mode 160000 index 88a9634b..00000000 --- a/external/paradigm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 88a9634b88e2f26075647225168e86bd028f7e50 diff --git a/external/project_utils b/external/project_utils deleted file mode 160000 index 2296e787..00000000 --- a/external/project_utils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2296e787d5046bcad7220ebe59850ae07c0291b4 diff --git a/external/pytest-mpi-check b/external/pytest-mpi-check deleted file mode 160000 index a6502571..00000000 --- a/external/pytest-mpi-check +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a6502571c72d592693c5ebec0d6a68781fbe2414 diff --git a/external/std_e b/external/std_e deleted file mode 160000 index 0e46452d..00000000 --- a/external/std_e +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e46452dfe64b76ce766b99af832e189b32cb271 diff --git a/maia/__init__.py b/maia/__init__.py deleted file mode 100644 index 357bc1a8..00000000 --- a/maia/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Maia: Distributed algorithms and manipulations over CGNS meshes -""" - -__version__ = '1.3' - -import Pypdm.Pypdm as PDM - -pdm_has_parmetis = PDM.pdm_has_parmetis -pdm_has_ptscotch = PDM.pdm_has_ptscotch -npy_pdm_gnum_dtype = PDM.npy_pdm_gnum_dtype -pdma_enabled = PDM.pdm_has_pdma - -from maia import algo -from maia import factory -from maia import io -from maia import pytree -from maia import transfer -from maia import utils - -# Change the default Python handling of uncaught exceptions -# By default, if one proc raises an uncaught exception, it may lead to deadlocks -# With `enable_mpi_excepthook`, if one proc raises an uncaught exception, MPI_Abort(1) is called -from maia.utils.parallel import excepthook -excepthook.enable_mpi_excepthook() diff --git a/maia/__old/generate/from_structured_grid.hpp b/maia/__old/generate/from_structured_grid.hpp deleted file mode 100644 index 751424e8..00000000 --- a/maia/__old/generate/from_structured_grid.hpp +++ /dev/null @@ -1,129 +0,0 @@ -#pragma once - - -#include -#include "std_e/future/algorithm.hpp" -#include "std_e/multi_index/cartesian_product_size.hpp" -#include "std_e/multi_index/multi_index_range.hpp" -#include "std_e/utils/concatenate.hpp" -#include -#include -#include -#include "std_e/future/ranges.hpp" -#include "std_e/future/ranges/repeat.hpp" -#include "std_e/future/ranges/concat.hpp" -#include "maia/algo/dist/elements_to_ngons/connectivity/from_structured_grid.hpp" -#include "maia/__old/generate/structured_grid_utils.hpp" - - -namespace maia { - - -// generate_cells { -template constexpr auto -generate_cells(const Multi_index& vertex_dims) { - auto gen_hex_8 = [&vertex_dims](const Multi_index& is){ return generate_hex_8(vertex_dims,is); }; - - Multi_index cell_dims = block_cell_dims(vertex_dims); - return - std_e::fortran_multi_index_range(cell_dims) - | std::views::transform(gen_hex_8); -} -// generate_cells } - -// generate_face { -// Generates connectivities along direction "d" -// Vertices on which faces are built are ordered along i,j,k following fortran order -// Generated faces are given by sheets normal to d, by fortran order -template auto -generate_faces_normal_to(const Multi_index& vertex_dims) { - STD_E_ASSERT(0<=d && d<3); - auto gen_quad_4 = [&vertex_dims](const Multi_index& is){ return generate_quad_4_normal_to(vertex_dims,is); }; - - Multi_index iter_order = {(d+1)%3,(d+2)%3,d}; // fill sheet by sheet, hence "d" is the last varying index - Multi_index face_sheets_dims = block_face_dims_normal_to(vertex_dims,d); - - return - std_e::multi_index_range_with_order(face_sheets_dims,iter_order) - | std::views::transform(gen_quad_4); -} - -template auto -generate_faces(const Multi_index& vertex_dims) { - return - std_e::views::concat( - generate_faces_normal_to<0>(vertex_dims), - generate_faces_normal_to<1>(vertex_dims), - generate_faces_normal_to<2>(vertex_dims) - ); -} -// generate_face } - - -// generate_parents { -template auto -generate_cell_parents(const Multi_index& vertex_dims, int d) { - STD_E_ASSERT(0<=d && d<3); - - Multi_index iter_order = {(d+1)%3,(d+2)%3,d}; // fill sheet by sheet, hence "d" is the last varying index - auto cell_dims = block_cell_dims(vertex_dims); - - auto gen_parent_cell_id = [cell_dims](const Multi_index& is){ return generate_parent_cell_id(cell_dims,is); }; - - return - std_e::multi_index_range_with_order(cell_dims,iter_order) - | std::views::transform(gen_parent_cell_id); -} - -template auto -generate_l_parents(const Multi_index& vertex_dims, int d) { - auto sheet_dims = dims_of_sheet_normal_to(vertex_dims,d); - auto sheet_sz = std_e::cartesian_product_size(sheet_dims); - - return - std_e::views::concat( - std_e::ranges::repeat(0,sheet_sz), // no left parents for first sheet - generate_cell_parents(vertex_dims,d) - ); -} - -template auto -generate_r_parents(const Multi_index& vertex_dims, int d) { - auto sheet_dims = dims_of_sheet_normal_to(vertex_dims,d); - auto sheet_sz = std_e::cartesian_product_size(sheet_dims); - - return - std_e::views::concat( - generate_cell_parents(vertex_dims,d), - std_e::ranges::repeat(0,sheet_sz) // no right parents for last sheet - ); -} - -template auto -generate_l_parents(const Multi_index& vertex_dims) { - return std_e::views::concat( - generate_l_parents(vertex_dims,0), - generate_l_parents(vertex_dims,1), - generate_l_parents(vertex_dims,2) - ); -} -template auto -generate_r_parents(const Multi_index& vertex_dims) { - return std_e::views::concat( - generate_r_parents(vertex_dims,0), - generate_r_parents(vertex_dims,1), - generate_r_parents(vertex_dims,2) - ); -} - -template auto -generate_faces_parent_cell_ids(const Multi_index& vertex_dims) { - return std_e::views::concat( - generate_l_parents (vertex_dims), - generate_r_parents(vertex_dims) - ); -} -// generate_parents } - - -} // maia diff --git a/maia/__old/generate/nfaces_from_ngons.cpp b/maia/__old/generate/nfaces_from_ngons.cpp deleted file mode 100644 index fd2a0152..00000000 --- a/maia/__old/generate/nfaces_from_ngons.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/generate/nfaces_from_ngons.hpp" - -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids/utils.hpp" -#include "std_e/algorithm/for_each.hpp" - - -namespace cgns { - - -template -struct cell_id_and_face_id { - I cell_id; - I face_id; -}; - -constexpr auto equal_cell_id = [](const auto& x, const auto& y){ return x.cell_id == y.cell_id; }; -constexpr auto less_cell_id = [](const auto& x, const auto& y){ return x.cell_id < y.cell_id; }; - -auto -face_ids_by_sorted_cell_ids(const tree& ngons) -> std::vector> { - STD_E_ASSERT(label(ngons)=="Elements_t"); - STD_E_ASSERT(element_type(ngons)==NGON_n); - - auto first_ngon_id = ElementRange(ngons)[0]; - auto parent_elts = ParentElements(ngons); - - std::vector> cell_face_ids; - for (I4 i=0; i auto -append_nface_from_range(Forward_it f, Forward_it l, Output_it nfaces) { - *nfaces++ = l-f; - std::transform(f,l,nfaces,[](const auto& x){ return x.face_id; }); -} - -template constexpr auto -// requires Range_function(Forward_it,Forward_it) -// requires Predicate_generator is a Function_generator -// requires Predicate_generator::function_type(Forward_it::value_type) -> bool -for_each_partition2(Forward_it first, S last, Bin_predicate eq, Range_function f) { - auto p_first = first; - while (p_first!=last) { - auto p_last = std_e::find_if(p_first,last,[=](auto& elt){ return !eq(*p_first,elt); }); - f(p_first,p_last); - - p_first = p_last; - } -} -auto -nfaces_from_cell_face(const std::vector>& cell_face_ids) -> tree { - STD_E_ASSERT(std::is_sorted(begin(cell_face_ids),end(cell_face_ids),less_cell_id)); - - auto first_non_boundary = std::partition_point(begin(cell_face_ids),end(cell_face_ids),[](auto& x){ return x.cell_id==0; }); - - std::vector nfaces; - I4 nb_nfaces = 0; - - auto append_nface = [&nb_nfaces,&nfaces](auto f, auto l){ append_nface_from_range(f,l,std::back_inserter(nfaces)); ++nb_nfaces; }; - for_each_partition2(first_non_boundary,end(cell_face_ids),equal_cell_id,append_nface); - - I4 first_nface_id = cell_face_ids[0].cell_id; - return new_NfaceElements("Nface",std::move(nfaces),first_nface_id,first_nface_id+nb_nfaces); -} - -auto -nfaces_from_ngons(const tree& ngons) -> tree { - auto cell_face_ids = face_ids_by_sorted_cell_ids(ngons); - return nfaces_from_cell_face(cell_face_ids); -} - -auto -add_nfaces_to_zone(tree& z) -> void { - tree& ngons = element_section(z,NGON_n); - emplace_child(z,nfaces_from_ngons(ngons)); -} - -auto -add_nfaces(tree& b) -> void { - for_each_unstructured_zone(b,add_nfaces_to_zone); -} - - -} // cgns -#endif // C++>17 diff --git a/maia/__old/generate/nfaces_from_ngons.hpp b/maia/__old/generate/nfaces_from_ngons.hpp deleted file mode 100644 index 6eed9399..00000000 --- a/maia/__old/generate/nfaces_from_ngons.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - - -namespace cgns { - -// Fwd decl -struct tree; - - -auto -add_nfaces(tree& b) -> void; - - -} // cgns diff --git a/maia/__old/generate/structured_grid_utils.hpp b/maia/__old/generate/structured_grid_utils.hpp deleted file mode 100644 index 14d39bab..00000000 --- a/maia/__old/generate/structured_grid_utils.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - - -#include "std_e/multi_index/multi_index.hpp" - - -template auto -block_cell_dims(const Multi_index& vtx_dims) -> Multi_index { - int rank = vtx_dims.size(); - auto nb_cells = std_e::make_array_of_size(rank); - for (int i=0; i auto -block_face_dims_normal_to(const Multi_index& vtx_dims, int d) -> Multi_index { - // Precondition: 0 <= d < rank - int rank = vtx_dims.size(); - Multi_index d_face_dims; - for (int dir=0; dir auto -dims_of_sheet_normal_to(const Multi_index& vtx_dims, int d) -> std_e::multi_index { - STD_E_ASSERT(0<=d && d<3); - if (d==0) return {vtx_dims[1]-1,vtx_dims[2]-1}; - if (d==1) return {vtx_dims[2]-1,vtx_dims[0]-1}; - if (d==2) return {vtx_dims[0]-1,vtx_dims[1]-1}; - throw; -} diff --git a/maia/__old/generate/test/from_structured_grid.test.cpp b/maia/__old/generate/test/from_structured_grid.test.cpp deleted file mode 100644 index fb8e2b78..00000000 --- a/maia/__old/generate/test/from_structured_grid.test.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - -#include "maia/__old/generate/structured_grid_utils.hpp" -#include "maia/__old/generate/from_structured_grid.hpp" -#include "maia/__old/utils/cgns_tree_examples/simple_meshes.hpp" - -using std::vector; -using std::array; -using namespace maia; - - - -TEST_CASE("generate for maia::six_hexa_mesh") { - using MI = std_e::multi_index; - MI vertex_dims = {4,3,2}; - - auto cells = generate_cells(vertex_dims) | std_e::to_vector(); - auto faces = generate_faces(vertex_dims) | std_e::to_vector(); - auto l_parents = generate_l_parents(vertex_dims) | std_e::to_vector(); - auto r_parents = generate_r_parents(vertex_dims) | std_e::to_vector(); - - CHECK( cells == maia::six_hexa_mesh::cell_vtx ); - CHECK( faces == maia::six_hexa_mesh::face_vtx ); - - CHECK( l_parents == maia::six_hexa_mesh::l_parents ); - CHECK( r_parents == maia::six_hexa_mesh::r_parents ); -}; -#endif // C++>17 diff --git a/maia/__old/generate/test/structured_grid_utils.test.cpp b/maia/__old/generate/test/structured_grid_utils.test.cpp deleted file mode 100644 index f35eebff..00000000 --- a/maia/__old/generate/test/structured_grid_utils.test.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "std_e/unit_test/doctest.hpp" - -#include "maia/__old/generate/structured_grid_utils.hpp" - -TEST_CASE("block_cell_dims") { - using MI = std_e::multi_index; - MI vertex_dims = {4,3,2}; - - MI cell_dims = block_cell_dims(vertex_dims); - CHECK( cell_dims == MI{3,2,1} ); -} - -TEST_CASE("block_face_dims_normal_to") { - using MI = std_e::multi_index; - SUBCASE("one_quad") { - MI vertex_dims = {2,2,2}; - auto nb_faces_i = block_face_dims_normal_to(vertex_dims,0); - auto nb_faces_j = block_face_dims_normal_to(vertex_dims,1); - auto nb_faces_k = block_face_dims_normal_to(vertex_dims,2); - - CHECK( nb_faces_i == MI{2,1,1} ); - CHECK( nb_faces_j == MI{1,2,1} ); - CHECK( nb_faces_k == MI{1,1,2} ); - } - SUBCASE("six_quads") { - MI vertex_dims = {4,3,2}; - auto nb_faces_i = block_face_dims_normal_to(vertex_dims,0); - auto nb_faces_j = block_face_dims_normal_to(vertex_dims,1); - auto nb_faces_k = block_face_dims_normal_to(vertex_dims,2); - - CHECK( nb_faces_i == MI{4,2,1} ); - CHECK( nb_faces_j == MI{3,3,1} ); - CHECK( nb_faces_k == MI{3,2,2} ); - } -} diff --git a/maia/__old/transform/base_renumbering.cpp b/maia/__old/transform/base_renumbering.cpp deleted file mode 100644 index 9f861eb0..00000000 --- a/maia/__old/transform/base_renumbering.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/transform/base_renumbering.hpp" - -namespace cgns { - -auto -register_connectivities_PointList_infos(tree& base, MPI_Comm comm) -> interzone_point_list_info { - zone_exchange ze(base,comm); - auto plds_by_zone = ze.send_PointListDonor_to_donor_proc(); - return {std::move(ze),std::move(plds_by_zone)}; -} - -auto -re_number_point_lists_donors(interzone_point_list_info& pl_infos) -> void { - pl_infos.ze.receive_PointListDonor_from_donor_proc(pl_infos.pld_by_z.plds); -} - -} // cgns -#endif // C++>17 diff --git a/maia/__old/transform/base_renumbering.hpp b/maia/__old/transform/base_renumbering.hpp deleted file mode 100644 index d130c458..00000000 --- a/maia/__old/transform/base_renumbering.hpp +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "std_e/future/span.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids/utils.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "maia/__old/utils/neighbor_graph.hpp" -#include "maia/__old/transform/donated_point_lists.hpp" -#include "std_e/future/zip.hpp" -#include "std_e/future/ranges/chunk_by.hpp" - -using namespace maia; // TODO - -namespace cgns { - - -struct interzone_point_list_info { - zone_exchange ze; - pl_by_donor_zone pld_by_z; -}; - -// TODO also return the GridLocation -inline auto find_point_list_by_zone_donor(pl_by_donor_zone& pl_by_z, const std::string& z_name) -> donated_point_lists { - // TODO use std::find_if (need iterators in jagged_range) - donated_point_lists res; - int nb_pl_donors = pl_by_z.donor_z_names.size(); - for (int i=0; i auto apply_base_renumbering(tree& b, Fun zone_renumbering, MPI_Comm comm) -> void; // TODO no default -auto register_connectivities_PointList_infos(tree& base, MPI_Comm comm) -> interzone_point_list_info; -auto re_number_point_lists_donors(interzone_point_list_info& pl_infos) -> void; -// declarations } - - -template auto -apply_base_renumbering(tree& b, Fun zone_renumbering, MPI_Comm comm) -> void { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - auto zs = get_children_by_label(b,"Zone_t"); - - interzone_point_list_info pl_infos; - if (std_e::n_rank(comm)>1) { // TODO clean (does not work for sequential with several GC) - pl_infos = register_connectivities_PointList_infos(b,comm); - } - - for (tree& z : zs) { - auto z_plds = find_point_list_by_zone_donor(pl_infos.pld_by_z,name(z)); - zone_renumbering(z,z_plds); - } - - if (std_e::n_rank(comm)>1) { // TODO clean - re_number_point_lists_donors(pl_infos); - } -} - -inline auto -symmetrize_grid_connectivities(tree& b, MPI_Comm comm) -> void { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - - auto zs = get_children_by_label(b,"Zone_t"); - - zone_exchange ze(b,comm); - // note: pl[i] and plds[i] belong to the same GC (they are matched together) // TODO zip everything - auto pls_by_zone = ze.send_PointList_to_donor_proc(); - auto plds_by_zone = ze.send_PointListDonor_to_donor_proc(); - - for (tree& z : zs) { - tree& zgc = cgns::get_child_by_name(z,"ZoneGridConnectivity"); - auto z_pls = find_point_list_by_zone_donor(pls_by_zone ,name(z)); - auto z_plds = find_point_list_by_zone_donor(plds_by_zone,name(z)); - std::ranges::sort(z_pls ,less_receiver_zone); - std::ranges::sort(z_plds,less_receiver_zone); - auto pls_by_recv_z = z_pls | std_e::chunk_by(eq_receiver_zone); - auto plds_by_recv_z = z_plds | std_e::chunk_by(eq_receiver_zone); - auto gc_by_recv_z = std_e::zip(pls_by_recv_z,plds_by_recv_z); - auto z_gcs = cgns::get_nodes_by_matching(zgc,"GridConnectivity_t"); - for (const auto& gcs : gc_by_recv_z) { - std::string receiver_z_name = gcs.first[0].receiver_z_name; - std::vector pl; - for (const auto& recv_pld : gcs.second) { // Note : using pl DONOR of the OPPOSITE zone, because they are the pl of the CURRENT zone - for (I4 i : recv_pld.pl) { - pl.push_back(i); - } - } - - std::vector pld; - for (const auto& recv_pl : gcs.first) { // Note : inverted for the same reason - for (I4 i : recv_pl.pl) { - pld.push_back(i); - } - } - - for (tree& z_gc : z_gcs) { - auto opp_z_name = to_string(value(z_gc)); - if (opp_z_name == receiver_z_name) { - auto z_pl = PointList(z_gc); - for (I4 i : z_pl) { - pl.push_back(i); - } - auto z_pld = PointListDonor(z_gc); - for (I4 i : z_pld) { - pld.push_back(i); - } - } - } - // TODO sort unique - - tree pl_node = new_PointList("PointList" ,std::move(pl )); - tree pld_node = new_PointList("PointListDonor",std::move(pld)); - tree new_gc = new_GridConnectivity("Sym_GC_"+receiver_z_name,receiver_z_name,"Vertex","Abutting1to1"); // TODO Vertex - emplace_child(new_gc,std::move(pl_node)); - emplace_child(new_gc,std::move(pld_node)); - emplace_child(zgc,std::move(new_gc)); - } - } - -} - - - -} // cgns diff --git a/maia/__old/transform/connectivity_range.hpp b/maia/__old/transform/connectivity_range.hpp deleted file mode 100644 index 349fdc4b..00000000 --- a/maia/__old/transform/connectivity_range.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - - -#include "std_e/data_structure/block_range/vblock_range.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" - - -namespace maia { - - -template - requires (std::is_same_v< std::remove_const_t , cgns::tree >) - auto -make_connectivity_range(Tree& elt_section) { - auto cs = cgns::ElementConnectivity(elt_section); - auto offsets = cgns::ElementStartOffset (elt_section); - return std_e::view_as_vblock_range2(cs,offsets); -} -template - requires (std::is_same_v< std::remove_const_t , cgns::tree >) - auto -make_connectivity_subrange(Tree& elt_section, I start, I finish) { - auto cs = cgns::ElementConnectivity(elt_section); - auto offsets = cgns::ElementStartOffset (elt_section); - - auto sub_offsets = std_e::make_span(offsets.data()+start,finish-start+1); - return std_e::view_as_vblock_range2(cs,sub_offsets); -} - - -} // maia diff --git a/maia/__old/transform/convert_to_std_elements.cpp b/maia/__old/transform/convert_to_std_elements.cpp deleted file mode 100644 index 4f5d16c2..00000000 --- a/maia/__old/transform/convert_to_std_elements.cpp +++ /dev/null @@ -1,394 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/transform/convert_to_std_elements.hpp" - - -#include "maia/__old/transform/put_boundary_first/boundary_ngons_at_beginning.hpp" // TODO rename element_section_partition -#include "maia/__old/transform/connectivity_range.hpp" -#include "cpp_cgns/sids/Hierarchical_Structures.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" -#include "std_e/future/span.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids/utils.hpp" -#include "std_e/data_structure/block_range/block_range.hpp" - - -using cgns::tree; -using cgns::I4; -using cgns::I8; - - -namespace maia { - - -template auto -make_ElementConnectivity_subrange(const cgns::tree& elt_section, I start, I finish) { - auto cs = cgns::ElementConnectivity(elt_section); - auto offsets = cgns::ElementStartOffset (elt_section); - - return std_e::make_span(cs.data()+offsets[start],offsets[finish]-offsets[start]); -} -template auto -convert_to_simple_exterior_boundary_connectivities(const tree& ngons, I n_tri) -> std::pair,I> { - I n_face = ElementSizeBoundary(ngons); - I n_quad = n_face-n_tri; - std::vector face_sections; - - auto tris = make_ElementConnectivity_subrange(ngons, I(0),n_tri ); - if (tris.size() > 0) { - tree tri_node = cgns::new_Elements( - "TRI_3", - cgns::TRI_3, - std::vector(begin(tris),end(tris)), - I(1),n_tri - ); - emplace_child(tri_node,cgns::new_ElementDistribution(std::vector{0,n_tri,n_tri})); - face_sections.emplace_back(std::move(tri_node)); - } - - auto quads = make_ElementConnectivity_subrange(ngons, n_tri,n_face); - if (quads.size() > 0) { - tree quad_node = cgns::new_Elements( - "QUAD_4", - cgns::QUAD_4, - std::vector(begin(quads),end(quads)), - n_tri+1,n_face - ); - emplace_child(quad_node,cgns::new_ElementDistribution(std::vector{0,n_quad,n_quad})); - face_sections.emplace_back(std::move(quad_node)); - } - - return { std::move(face_sections) , n_face+1 }; -} - -template auto -find_vertex_not_in_first(const T& connec_0, const T& connec_1) { - for (auto vertex : connec_1) { - if (std::find(begin(connec_0),end(connec_0),vertex)==end(connec_0)) return vertex; - } - STD_E_ASSERT(false); return connec_0[0]; -} - - -template auto -convert_to_tet_vtx(const auto& tet_face, const tree& ngons, I first_cell_id, I next_avail_id) -> tree { - auto face_vtx = make_connectivity_range(ngons); - auto first_ngon_id = cgns::ElementRange(ngons)[0]; - auto pe = cgns::ParentElements(ngons); - - I n_tet = tet_face.size(); - I n_vtx = n_tet*4; - std::vector tet_vtx(n_vtx); - auto d_first = tet_vtx.data(); - - for (I k=0; k{0,n_tet,n_tet})); - return tet_node; -} -template auto -convert_to_pyra_vtx(const auto& pyra_face, const tree& ngons, I first_cell_id, I next_avail_id) -> tree { - auto face_vtx = make_connectivity_range(ngons); - auto first_ngon_id = cgns::ElementRange(ngons)[0]; - auto pe = cgns::ParentElements(ngons); - - I n_pyra = pyra_face.size(); - I n_vtx = n_pyra*5; - std::vector pyra_vtx(n_vtx); - auto d_first = pyra_vtx.data(); - - for (I k=0; k{0,n_pyra,n_pyra})); - return pyra_node; -} - - -template auto -contains_vertex(I vtx, const Node_connectivity& c) -> bool { - return std::find(begin(c),end(c),vtx) != end(c); -} - -template auto -other_common(I vtx, const Quad& quad0, const Quad& quad1) -> I { - std::array q0; - std::copy(begin(quad0),end(quad0),begin(q0)); - std::sort(begin(q0),end(q0)); - std::array q1; - std::copy(begin(quad1),end(quad1),begin(q1)); - std::sort(begin(q1),end(q1)); - std::array q0_inter_q1; - std::set_intersection(begin(q0),end(q0),begin(q1),end(q1),begin(q0_inter_q1)); - if (q0_inter_q1[0]!=vtx) return q0_inter_q1[0]; - else { STD_E_ASSERT(q0_inter_q1[0]==vtx); return q0_inter_q1[1]; } -} - -template auto -node_above(I vtx, const T& quads) -> I { - auto contains_vtx = [vtx](const auto& quad){ return contains_vertex(vtx,quad); }; - auto quad0 = std::find_if(begin(quads),end(quads),contains_vtx); - auto quad1 = std::find_if(quad0+1,end(quads),contains_vtx); - STD_E_ASSERT(quad0!=end(quads)); - STD_E_ASSERT(quad1!=end(quads)); - return other_common(vtx,*quad0,*quad1); -} - -template auto -convert_to_prism_vtx(const auto& prism_face, const tree& ngons, I first_cell_id, I next_avail_id) -> tree { - auto face_vtx = make_connectivity_range(ngons); - auto first_ngon_id = cgns::ElementRange(ngons)[0]; - auto pe = cgns::ParentElements(ngons); - - I n_prism = prism_face.size(); - I n_vtx = n_prism*6; - std::vector prism_vtx(n_vtx); - auto d_first = prism_vtx.data(); - - for (I k=0; k tri; - std::array,3> quads; - int quad_pos = 0; - for (const auto& face_id : prism) { - I face_idx = face_id-first_ngon_id; - auto face = face_vtx[face_idx]; - if (face.size()==3) { - if (tri_idx==not_found) { - tri_idx = face_idx; - std::copy(begin(face),end(face),begin(tri)); - } - } else { - quads[quad_pos] = face; - ++quad_pos; - } - } - // use the tri as nodes 1,2,3 - if (pe(tri_idx,0)==prism_id) { // outward normal - std::reverse(begin(tri),end(tri)); - } else { - STD_E_ASSERT(pe(tri_idx,1)==prism_id); - } - d_first = std::copy(begin(tri),end(tri),d_first); - - // nodes "above" tri - *d_first++ = node_above(tri[0],quads); - *d_first++ = node_above(tri[1],quads); - *d_first++ = node_above(tri[2],quads); - } - - tree penta_node = cgns::new_Elements( - "PENTA_6", - cgns::PENTA_6, - std::move(prism_vtx), - next_avail_id,next_avail_id+n_prism-1 - ); - emplace_child(penta_node,cgns::new_ElementDistribution(std::vector{0,n_prism,n_prism})); - return penta_node; -} - -template auto -share_vertices(const Connecivity_type_0& c0, const Connecivity_type_1& c1) -> bool { - // TODO replace by std_e::set_intersection_size if size_0*size_1 >> size*log(size) - for (auto x : c0) { - for (auto y : c1) { - if (x==y) return true; - } - } - return false; -} - -template auto -convert_to_hexa_vtx(const auto& hexa_face, const tree& ngons, I first_cell_id, I next_avail_id) -> tree { - auto face_vtx = make_connectivity_range(ngons); - auto first_ngon_id = cgns::ElementRange(ngons)[0]; - auto pe = cgns::ParentElements(ngons); - - I n_hexa = hexa_face.size(); - I n_vtx = n_hexa*8; - std::vector hexa_vtx(n_vtx); - auto d_first = hexa_vtx.data(); - - for (int k=0; k quad_0; - I quad_0_idx = hexa[0]-first_ngon_id; - auto quad_0_in_ngon = face_vtx[quad_0_idx]; - std::copy(begin(quad_0_in_ngon),end(quad_0_in_ngon),begin(quad_0)); - - std::array,4> side_quads; - int side_quad_pos = 0; - for (int i=0; i<6; ++i) { - I quad_idx = hexa[i]-first_ngon_id; - auto quad = face_vtx[quad_idx]; - STD_E_ASSERT(quad.size()==4); - } - for (int i=1; i<6; ++i) { - I quad_idx = hexa[i]-first_ngon_id; - auto quad = face_vtx[quad_idx]; - if (share_vertices(quad,quad_0)) { - side_quads[side_quad_pos] = quad; - ++side_quad_pos; - } - } - - // use quad_0 as nodes 1,2,3,4 - if (pe(quad_0_idx,0)==hexa_id) { // outward normal - std::reverse(begin(quad_0),end(quad_0)); - } else { - STD_E_ASSERT(pe(quad_0_idx,1)==hexa_id); - } - d_first = std::copy(begin(quad_0),end(quad_0),d_first); - - // nodes "above" quad_0 - *d_first++ = node_above(quad_0[0],side_quads); - *d_first++ = node_above(quad_0[1],side_quads); - *d_first++ = node_above(quad_0[2],side_quads); - *d_first++ = node_above(quad_0[3],side_quads); - } - - tree hex_node = cgns::new_Elements( - "HEXA_8", - cgns::HEXA_8, - std::move(hexa_vtx), - next_avail_id,next_avail_id+n_hexa-1 - ); - emplace_child(hex_node,cgns::new_ElementDistribution(std::vector{0,n_hexa,n_hexa})); - return hex_node; -} - -template auto -convert_to_simple_volume_connectivities(const tree& ngons, const tree& nfaces, const std::vector cell_partition_indices, I next_avail_id) -> std::vector { - auto tet_face = make_connectivity_subrange(nfaces,cell_partition_indices[0],cell_partition_indices[1]); - auto pyra_face = make_connectivity_subrange(nfaces,cell_partition_indices[1],cell_partition_indices[2]); - auto prism_face = make_connectivity_subrange(nfaces,cell_partition_indices[2],cell_partition_indices[3]); - auto hexa_face = make_connectivity_subrange(nfaces,cell_partition_indices[3],cell_partition_indices[4]); - I first_cell_id = cgns::ElementRange(nfaces)[0]; - - std::vector cell_sections; - - if (tet_face.size()>0) { - cell_sections.push_back(convert_to_tet_vtx(tet_face,ngons,first_cell_id,next_avail_id)); - next_avail_id += tet_face.size(); - first_cell_id += tet_face.size(); - } - - if (pyra_face.size()>0) { - cell_sections.push_back(convert_to_pyra_vtx(pyra_face,ngons,first_cell_id,next_avail_id)); - next_avail_id += pyra_face.size(); - first_cell_id += pyra_face.size(); - } - - if (prism_face.size()>0) { - cell_sections.push_back(convert_to_prism_vtx(prism_face,ngons,first_cell_id,next_avail_id)); - next_avail_id += prism_face.size(); - first_cell_id += prism_face.size(); - } - - if (hexa_face.size()>0) { - cell_sections.push_back(convert_to_hexa_vtx(hexa_face,ngons,first_cell_id,next_avail_id)); - next_avail_id += hexa_face.size(); - first_cell_id += hexa_face.size(); - } - - return cell_sections; -} - - -template auto -_convert_zone_to_std_elements(tree& z) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - - tree& ngons = element_section(z,cgns::NGON_n); - tree& nfaces = element_section(z,cgns::NFACE_n); - auto face_pls = cgns::get_zone_point_lists(z,"FaceCenter"); - - // The NFace node should be signed to give the normal directions - // We need this information when constructing cell_vtx from cell_face. - // However, sometimes the NFace is not SIDS-compliant - // So we retrieve the information through the ParentElement - // Since it simplifies the treatment (when renumbering the NFace), we make the NFace unsigned - // (Note: since the treatment is in-place, the NFace is deleted at the end anyways) - auto cell_face_cs = cgns::ElementConnectivity(nfaces); - for (I& i_face : cell_face_cs) { - i_face = std::abs(i_face); - } - - // partition faces and cells - permute_boundary_ngons_at_beginning(ngons,nfaces,face_pls); - auto last_tri_index = partition_bnd_faces_by_number_of_vertices(ngons,nfaces,face_pls); - auto cell_partition_indices = partition_cells_into_simple_types(ngons,nfaces); - - // convert to simple connectivities - auto [bnd_elt_sections,next_avail_id] = convert_to_simple_exterior_boundary_connectivities(ngons,last_tri_index); - auto vol_elt_sections = convert_to_simple_volume_connectivities(ngons,nfaces,cell_partition_indices,next_avail_id); - - // update tree - rm_children_by_names(z,{name(ngons),name(nfaces)}); - - emplace_children(z,std::move(bnd_elt_sections)); - emplace_children(z,std::move(vol_elt_sections)); -} - -auto -convert_zone_to_std_elements(tree& z) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); // TODO is_gc_maia_zone - - if (value(z).data_type()=="I4") return _convert_zone_to_std_elements(z); - if (value(z).data_type()=="I8") return _convert_zone_to_std_elements(z); - throw cgns::cgns_exception("Zone "+name(z)+" has a value of data type "+value(z).data_type()+" but it should be I4 or I8"); -} - - -} // maia -#endif // C++>17 diff --git a/maia/__old/transform/convert_to_std_elements.hpp b/maia/__old/transform/convert_to_std_elements.hpp deleted file mode 100644 index a2695d62..00000000 --- a/maia/__old/transform/convert_to_std_elements.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include "cpp_cgns/cgns.hpp" - -namespace maia { - - -auto -convert_zone_to_std_elements(cgns::tree& z) -> void; - - -} // maia diff --git a/maia/__old/transform/donated_point_lists.hpp b/maia/__old/transform/donated_point_lists.hpp deleted file mode 100644 index 3386908e..00000000 --- a/maia/__old/transform/donated_point_lists.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include "std_e/future/span.hpp" -#include "cpp_cgns/sids/Building_Block_Structure_Definitions.hpp" - -namespace maia { - -// PointList values coming from a receiver zone (that this receiver zone stores in a GridConnectivity) -// TODO multi_range -struct donated_point_list { - std::string receiver_z_name; - cgns::GridLocation_t loc; - std_e::span pl; -}; -const auto eq_receiver_zone = [](const donated_point_list& x, const donated_point_list& y) { - return x.receiver_z_name == y.receiver_z_name ; -}; -const auto less_receiver_zone = [](const donated_point_list& x, const donated_point_list& y) { - return x.receiver_z_name < y.receiver_z_name ; -}; - -using donated_point_lists = std::vector; - -} // maia diff --git a/maia/__old/transform/put_boundary_first/boundary_ngons_at_beginning.hpp b/maia/__old/transform/put_boundary_first/boundary_ngons_at_beginning.hpp deleted file mode 100644 index 8d089c4e..00000000 --- a/maia/__old/transform/put_boundary_first/boundary_ngons_at_beginning.hpp +++ /dev/null @@ -1,238 +0,0 @@ -#pragma once - - -#include "std_e/algorithm/algorithm.hpp" -#include -#include -#include -#include "cpp_cgns/sids/utils.hpp" -#include "std_e/algorithm/id_permutations.hpp" -#include "maia/utils/logging/log.hpp" -#include "maia/__old/transform/put_boundary_first/permute.hpp" -#include "maia/__old/transform/renumber_point_lists.hpp" // TODO move in renumber/ -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "std_e/algorithm/mismatch_points.hpp" -#include "std_e/data_structure/block_range/vblock_range.hpp" -#include "std_e/data_structure/block_range/vblock_permutation.hpp" -#include "maia/__old/transform/connectivity_range.hpp" - - -using cgns::tree; -using cgns::ParentElements; - - -namespace maia { - - -// DOC the returned boundary/interior partition is *stable* -template auto -boundary_interior_permutation(const cgns::md_array_view& parent_elts) -> std::pair,I> { - I n_connec = parent_elts.extent(0); - STD_E_ASSERT(parent_elts.extent(1)==2); - - // init - auto perm = std_e::iota_vector(n_connec); - - // permute - auto connectivity_is_on_boundary = [&parent_elts](I i){ return cgns::is_boundary(parent_elts,i); }; - auto partition_sub_rng = std::ranges::stable_partition(perm,connectivity_is_on_boundary); - I partition_index = partition_sub_rng.begin() - perm.begin(); - - return {perm,partition_index}; -} - - -template auto -permute_boundary_ngons_at_beginning(tree& ngons, tree& nfaces, const std::vector>& pls) -> I { - STD_E_ASSERT(label(ngons)=="Elements_t"); - STD_E_ASSERT(element_type(ngons)==cgns::NGON_n); - - if (cgns::is_boundary_partitioned_element_section(ngons)) return ElementSizeBoundary(ngons); - - auto parent_elts = cgns::ParentElements(ngons); - auto face_vtx = make_connectivity_range(ngons); - - // compute permutation - auto [perm,partition_index] = boundary_interior_permutation(parent_elts); - - // apply permutation - std_e::permute_vblock_range(face_vtx,perm); - permute_parent_elements(parent_elts,perm); - I offset = cgns::ElementRange(ngons)[0]; - auto cell_face_cs = cgns::ElementConnectivity(nfaces); - inv_permute_connectivity(cell_face_cs,perm,offset); - - // TODO bundle with inv_permute_connectivity(cell_face_cs,perm); - auto inv_p = std_e::inverse_permutation(perm); - std_e::offset_permutation elts_perm(offset,inv_p); - renumber_point_lists(pls,elts_perm); - - // record number of bnd elements - ElementSizeBoundary(ngons) = partition_index; - - return partition_index; -} - - -// ================= -// NGons -template auto -indirect_partition_by_number_of_vertices(const Rng& face_vtx_offsets) -> std::pair,I> { - // Precondition: only tet/pyra/prism/hexa - auto faces_n_vtx = std_e::interval_lengths(face_vtx_offsets); - STD_E_ASSERT(std::ranges::all_of(faces_n_vtx,[](I n_vtx){ return n_vtx==3 || n_vtx==4; })); - - // partition permutation - auto [perm,partition_indices] = std_e::indirect_partition_by_block_size(face_vtx_offsets); - - // get tri/quad split TODO: retrieve them from partition_indices - auto last_tri = std::ranges::find_if_not(perm,[&faces_n_vtx](I i){ return faces_n_vtx[i]==3; }); - - return std::make_pair(std::move(perm),last_tri-perm.begin()); -} - -template auto -create_permuted_ngon_connectivities(std_e::span old_connectivities, std_e::span old_eso, const std::vector& permutation) - -> std::vector -{ - auto _ = maia_time_log("create_permuted_ngon_connectivities"); - - // prepare accessors - auto old_ngon_accessor = std_e::view_as_vblock_range(old_connectivities,old_eso); - - std::vector new_connectivities(old_ngon_accessor.size()); - std::vector new_eso(old_eso.size()); - auto new_ngon_accessor = std_e::view_as_vblock_range(new_connectivities,new_eso); - - // permute - std_e::permute_copy_n(old_ngon_accessor.begin(),new_ngon_accessor.begin(),permutation.begin(),permutation.size()); - - return new_connectivities; -} - -template auto -apply_permutation_to_ngon(std_e::span old_ngon_cs, std_e::span old_eso, const std::vector& permutation) -> void { - auto new_connectivities = create_permuted_ngon_connectivities(old_ngon_cs,old_eso,permutation); - std::ranges::copy(new_connectivities,old_ngon_cs.begin()); -} - -template auto -partition_bnd_faces_by_number_of_vertices(tree& ngons, tree& nfaces, const std::vector>& pls) -> I { - // Preconditions: - STD_E_ASSERT(label(ngons)=="Elements_t"); - STD_E_ASSERT(element_type(ngons)==cgns::NGON_n); - I n_bnd_faces = ElementSizeBoundary(ngons); - auto parent_elts = ParentElements(ngons); - STD_E_ASSERT(n_bnd_faces != 0); - - auto bnd_face_vtx = make_connectivity_subrange(ngons,I(0),n_bnd_faces); - - // compute permutation - auto [perm,last_tri_index] = indirect_partition_by_number_of_vertices(bnd_face_vtx.offsets()); - - // apply permutation - std_e::permute_vblock_range(bnd_face_vtx,perm); - permute_parent_elements(parent_elts,perm); // TODO supposed to work with sub-arrays? - - I offset = cgns::ElementRange(ngons)[0]; - auto cell_face_cs = cgns::ElementConnectivity(nfaces); - inv_permute_connectivity_sub(cell_face_cs,perm,offset); // TODO either _sub not needed, nor renumber_point_lists (below) also needs that - - // TODO bundle with inv_permute_connectivity(cell_face_cs,perm); - auto inv_p = std_e::inverse_permutation(perm); - std_e::offset_permutation elts_perm(offset,inv_p); - renumber_point_lists(pls,elts_perm); - - return last_tri_index; -} - -// ================= -// NFaces -template auto -indirect_partition_by_number_of_faces(const Rng& cell_face_offsets) -> std::tuple,I,I> { - // Precondition: only tet/pyra/prism/hexa - auto cell_n_faces = std_e::interval_lengths(cell_face_offsets); - STD_E_ASSERT(std::ranges::all_of(cell_n_faces,[](I n_face){ return 4<=n_face && n_face<= 6; })); - - // partition permutation - auto [perm,partition_indices] = std_e::indirect_partition_by_block_size(cell_face_offsets); - - // get tet/pyra-prism and pyra-prism/hex splits TODO: retrieve them from partition_indices - auto last_tet = std::ranges::find_if_not(perm ,[&cell_n_faces](I i){ return cell_n_faces[i]==4; }); - auto last_pyra_prism = std::ranges::find_if_not(last_tet,end(perm),[&cell_n_faces](I i){ return cell_n_faces[i]==5; }); - - return std::make_tuple( std::move(perm) , last_tet-begin(perm) , last_pyra_prism-begin(perm) ); -} - -template auto -partition_cells_by_number_of_faces(auto& cell_face, auto& face_cell, I first_cell_id) -> std::pair { - auto [perm,last_tet,last_pyra_prism] = indirect_partition_by_number_of_faces(cell_face.offsets()); - std_e::permute_vblock_range(cell_face,perm); - inv_permute_parent_elements(face_cell,perm,first_cell_id); - return {last_tet,last_pyra_prism}; -} - - -template auto -is_pyra_among_pyra_prism(const auto& cell_face_connec, const auto& face_vtx, I first_face_id){ - STD_E_ASSERT(cell_face_connec.size()==5); - int n_quad = 0; - for (I face_id : cell_face_connec) { - I face_idx = face_id-first_face_id; - auto&& face = face_vtx[face_idx]; - if (face.size()==4) { - ++n_quad; - } else { - STD_E_ASSERT(face.size()==3); - } - } - return n_quad==1; -}; - -template auto -pyra_prism_permutation(auto& cell_face, const auto& face_vtx, I first_face_id) -> std::pair,I> { - auto is_pyra = [&cell_face,&face_vtx,first_face_id](I i){ return is_pyra_among_pyra_prism(cell_face[i],face_vtx,first_face_id); }; - auto n_connec = cell_face.size(); - auto perm = std_e::iota_vector(n_connec); - auto [partition_point,_] = std::ranges::stable_partition(perm,is_pyra); - I partition_index = partition_point - perm.begin(); - - return {perm,partition_index}; -} -template auto -partition_cells_pyra_prism(auto& cell_face, auto& face_cell, I first_pyra_id, const auto& face_vtx, I first_face_id) -> I { - auto [perm,first_prism_index] = pyra_prism_permutation(cell_face,face_vtx,first_face_id); - std_e::permute_vblock_range(cell_face,perm); - I n_pyra_prism = cell_face.size(); - inv_permute_parent_elements_sub(face_cell,perm,first_pyra_id,n_pyra_prism); - return first_prism_index; -} - -template auto -partition_cells_into_simple_types(tree& ngons, tree& nfaces) -> std::vector { - // Precondition: the cells described by nfaces are of type Tet4, Pyra5, Prism6 or Hex8 - - // 0. queries - I first_face_id = cgns::ElementRange(ngons)[0]; - I first_cell_id = cgns::ElementRange(nfaces)[0]; - - auto face_vtx = make_connectivity_range(ngons); - auto cell_face = make_connectivity_range(nfaces); - auto face_cell = ParentElements(ngons); - - // 1. apply partition // NOTE: not applied to PointList (so, wrong if PL for GridLoc==CellCenter) TODO do it! (see e.g. partition_bnd_faces_by_number_of_vertices) - /// 1.0. first partition by number of faces - auto [last_tet_index,last_pyra_prism_index] = partition_cells_by_number_of_faces(cell_face,face_cell,first_cell_id); - /// 1.1. still need to distinguish pyra/prism - auto pyra_prism_cell_face = make_connectivity_subrange(nfaces,last_tet_index,last_pyra_prism_index); - auto first_pyra_id = first_cell_id+last_tet_index; - - I first_prism_among_pyra_prism_index = partition_cells_pyra_prism(pyra_prism_cell_face,face_cell,first_pyra_id,face_vtx,first_face_id); - I first_prism_index = last_tet_index + first_prism_among_pyra_prism_index; - return { 0, last_tet_index, first_prism_index, last_pyra_prism_index, cell_face.size() }; -} - - -} // cgns diff --git a/maia/__old/transform/put_boundary_first/boundary_vertices.cpp b/maia/__old/transform/put_boundary_first/boundary_vertices.cpp deleted file mode 100644 index 39ef2fa9..00000000 --- a/maia/__old/transform/put_boundary_first/boundary_vertices.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/transform/put_boundary_first/boundary_vertices.hpp" - -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" -#include "cpp_cgns/sids/elements_utils.hpp" - -#include "std_e/utils/concatenate.hpp" -#include "std_e/utils/vector.hpp" -#include "cpp_cgns/sids/Hierarchical_Structures.hpp" - -#include "std_e/data_structure/block_range/block_range.hpp" -#include "std_e/data_structure/block_range/vblock_range.hpp" -#include "cpp_cgns/sids/utils.hpp" - - -using cgns::tree; -using cgns::tree_range; - - -namespace maia { - - -template auto -element_section_boundary_vertices(const tree& elt_section) -> std::vector { - STD_E_ASSERT(label(elt_section)=="Elements_t"); - - auto elt_type = element_type(elt_section); - STD_E_ASSERT(elt_type!=cgns::MIXED); - // Other precondition: dimension == 3 - - auto elt_vtx = cgns::ElementConnectivity(elt_section); - if (elt_type==cgns::NGON_n) { - auto pe = cgns::ParentElements(elt_section); - auto eso = cgns::ElementStartOffset(elt_section); - auto cs = std_e::view_as_vblock_range(elt_vtx,eso); - return find_boundary_vertices(cs,pe); - } else if (cgns::element_dimension(elt_type)==2) { - I n_bnd_elts = cgns::ElementSizeBoundary(elt_section); - if (n_bnd_elts!=0) { - I n_bnd_vertices = n_bnd_elts * cgns::number_of_vertices(elt_type); - return std::vector(begin(elt_vtx),begin(elt_vtx)+n_bnd_vertices); - } else { - // while it can be that the section is of mixed interior/exterior faces, - // in all likelihood, all the elements are on the boundary - // so we treat it so - return std::vector(begin(elt_vtx),end(elt_vtx)); - // TODO: pre-treat this case in Maia/CGNS conversion? (by fixing ElementSizeBoundary=n_elem in Maia) - } - } else { // 1D or 3D element - return {}; - } -} - -template auto -get_ordered_boundary_vertex_ids(const tree_range& element_sections) -> std::vector { - /* Precondition: */ for ([[maybe_unused]] const tree& e : element_sections) { STD_E_ASSERT(label(e)=="Elements_t"); } - // Post-condition: the boundary nodes are unique and sorted - - std::vector boundary_vertex_ids; - - for(const tree& elt_section: element_sections) { - std_e::append(boundary_vertex_ids, element_section_boundary_vertices(elt_section)); - } - - std_e::sort_unique(boundary_vertex_ids); - - return boundary_vertex_ids; -} - - -// Explicit instanciations of functions defined in this .cpp file -template auto get_ordered_boundary_vertex_ids(const tree_range& element_sections) -> std::vector; -template auto get_ordered_boundary_vertex_ids(const tree_range& element_sections) -> std::vector; - - - -} // maia -#endif // C++>17 diff --git a/maia/__old/transform/put_boundary_first/boundary_vertices.hpp b/maia/__old/transform/put_boundary_first/boundary_vertices.hpp deleted file mode 100644 index c2daacd4..00000000 --- a/maia/__old/transform/put_boundary_first/boundary_vertices.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" - - -namespace maia { - - -template auto -find_boundary_vertices(const Rng& cs, const PE_array& pe) { - STD_E_ASSERT(size_t(cs.size()) == size_t(pe.extent(0))); - STD_E_ASSERT(pe.extent(1) == 2); - using I = typename PE_array::value_type; - std::vector bnd_vertices; - - I n = cs.size(); - for (I i=0; i auto get_ordered_boundary_vertex_ids(const cgns::tree_range& element_sections) -> std::vector; - - -} // maia diff --git a/maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.cpp b/maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.cpp deleted file mode 100644 index 8354ce10..00000000 --- a/maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.hpp" - -#include "maia/utils/logging/log.hpp" -#include "std_e/algorithm/id_permutations.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" - - -using cgns::tree; -using cgns::I4; -using cgns::I8; - - -namespace maia { - - -template auto -vertex_permutation_to_move_boundary_at_beginning(I n_vtx, const std::vector& boundary_vertex_ids) -> std::vector { - std::vector vertices_are_on_boundary(n_vtx,false); - for (auto boundary_vertex_id : boundary_vertex_ids) { - I boundary_vertex_index = boundary_vertex_id - 1; // C++ is 0-indexed, CGNS ids are 1-indexed - vertices_are_on_boundary[boundary_vertex_index] = true; - } - - auto vertex_permutation = std_e::iota_vector(n_vtx); // init with no permutation - std::ranges::partition(vertex_permutation,[&](auto i){ return vertices_are_on_boundary[i]; }); - return vertex_permutation; -} - - -template auto -update_ids_for_elt_type(tree& elt_section, const std::vector& vertex_permutation) -> void { - auto elt_vtx = cgns::ElementConnectivity(elt_section); - - auto perm_old_to_new = std_e::inverse_permutation(vertex_permutation); - I offset = 1; // CGNS ids begin at 1 - std_e::offset_permutation perm(offset,perm_old_to_new); - std_e::apply(perm,elt_vtx); -} - -template auto -re_number_vertex_ids_in_elements(tree& elt_section, const std::vector& vertex_permutation) -> void { - // Preconditions - // - vertex_permutation is an index permutation (i.e. sort(permutation) == std_e::iota(permutation.size())) - // - any vertex "v" of "elt_section" is referenced in "vertex_permutation", - // i.e. vertex_permutation[v-1] is valid ("-1" because of 1-indexing) - auto _ = maia_time_log("re_number_vertex_ids_in_elements"); - - update_ids_for_elt_type(elt_section,vertex_permutation); -} - - -// explicit instanciations (do not pollute the header for only 2 instanciations) -template auto vertex_permutation_to_move_boundary_at_beginning(I4 n_vtx, const std::vector& boundary_vertex_ids) -> std::vector; -template auto vertex_permutation_to_move_boundary_at_beginning(I8 n_vtx, const std::vector& boundary_vertex_ids) -> std::vector; -template auto re_number_vertex_ids_in_elements(tree& elt_section, const std::vector& vertex_permutation) -> void; -template auto re_number_vertex_ids_in_elements(tree& elt_section, const std::vector& vertex_permutation) -> void; - -} // maia -#endif // C++>17 diff --git a/maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.hpp b/maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.hpp deleted file mode 100644 index 87d8f880..00000000 --- a/maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include "cpp_cgns/cgns.hpp" - -namespace maia { - -template auto -vertex_permutation_to_move_boundary_at_beginning(I nb_of_vertices, const std::vector& boundary_vertex_ids) -> std::vector; - -template auto -re_number_vertex_ids_in_elements(cgns::tree& elt_section, const std::vector& vertex_permutation) -> void; - -} // maia diff --git a/maia/__old/transform/put_boundary_first/permute.hpp b/maia/__old/transform/put_boundary_first/permute.hpp deleted file mode 100644 index 9e2b4a1d..00000000 --- a/maia/__old/transform/put_boundary_first/permute.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" - - -namespace maia { - - -// TODO test -template auto -permute_parent_elements(cgns::md_array_view& parent_elts, const std::vector& permutation) -> void { - std_e::permute(column(parent_elts,0).begin(),permutation); - std_e::permute(column(parent_elts,1).begin(),permutation); -} -template auto -inv_permute_parent_elements(cgns::md_array_view& parent_elts, const std::vector& permutation, I offset) -> void { - auto inv_p = std_e::inverse_permutation(permutation); - std_e::offset_permutation op(offset,inv_p); - auto not_bnd_parent = [](I pe){ return pe != 0; }; - std_e::apply_if(op,parent_elts,not_bnd_parent); -} -template auto // TODO factor with above -inv_permute_parent_elements_sub(cgns::md_array_view& parent_elts, const std::vector& permutation, I offset, I n) -> void { - auto inv_p = std_e::inverse_permutation(permutation); - std_e::offset_permutation op(offset,inv_p); - auto in_inter = [offset,n](I pe){ return pe!=0 && offset <= pe && pe < offset+n; }; - std_e::apply_if(op,parent_elts,in_inter); -} -template auto -inv_permute_connectivity(auto& cs, const std::vector& permutation, I offset) -> void { - auto inv_p = std_e::inverse_permutation(permutation); - std_e::offset_permutation op(offset,inv_p); - std_e::apply(op,cs); -} -template auto -inv_permute_connectivity_sub(auto& cs, const std::vector& permutation, I offset) -> void { // TODO rename _start?? - auto inv_p = std_e::inverse_permutation(permutation); - std_e::offset_permutation op(offset,inv_p); - I last_id = offset+permutation.size(); - auto affected_by_permutation = [last_id](I i){ return i < last_id; }; - std_e::apply_if(op,cs,affected_by_permutation); -} - - -} // maia diff --git a/maia/__old/transform/put_boundary_first/put_boundary_first.cpp b/maia/__old/transform/put_boundary_first/put_boundary_first.cpp deleted file mode 100644 index 24a9b29b..00000000 --- a/maia/__old/transform/put_boundary_first/put_boundary_first.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/transform/put_boundary_first/put_boundary_first.hpp" - -#include "maia/__old/transform/put_boundary_first/boundary_vertices.hpp" -#include "maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.hpp" -#include "maia/__old/transform/put_boundary_first/boundary_ngons_at_beginning.hpp" - -#include "cpp_cgns/sids/Hierarchical_Structures.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" -#include "maia/__old/transform/base_renumbering.hpp" -#include "maia/__old/transform/renumber_point_lists.hpp" - - -using cgns::tree; -using cgns::tree_range; -using cgns::I4; -using cgns::I8; - - -namespace maia { - - -// TODO move -template auto -_permute_node_value(cgns::node_value& x, const std::vector& perm) -> void { - auto x_span = cgns::view_as_span(x); - std_e::permute(x_span.begin(), perm); -}; -template auto -permute_node_value(cgns::node_value& x, const std::vector& perm) -> void { - if (x.data_type()=="I4") return _permute_node_value(x,perm); - if (x.data_type()=="I8") return _permute_node_value(x,perm); - if (x.data_type()=="R4") return _permute_node_value(x,perm); - if (x.data_type()=="R8") return _permute_node_value(x,perm); - throw - cgns::cgns_exception( - std::string(__func__) - + ": CGNS node has a value of data type " + x.data_type() - + " but it should be I4, I8, R4 or R8" - ); -}; -// end TODO move - - -template auto -permute_boundary_grid_coords_at_beginning(tree& grid_coords, const std::vector& vertex_permutation) -> void { - STD_E_ASSERT(label(grid_coords)=="GridCoordinates_t"); - auto coords = get_children_by_label(grid_coords,"DataArray_t"); - for (tree& coord : coords) { - permute_node_value(value(coord),vertex_permutation); - } -} - - -template auto -save_partition_point(tree& z, I nb_of_boundary_vertices) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - cgns::VertexBoundarySize_U(z) = nb_of_boundary_vertices; -} - - -template auto -permute_boundary_vertices_at_beginning(tree& z, const std::vector& boundary_vertex_ids) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - auto nb_of_vertices = cgns::VertexSize_U(z); - auto vertex_permutation = vertex_permutation_to_move_boundary_at_beginning(nb_of_vertices, boundary_vertex_ids); - - auto& grid_coords = get_child_by_name(z,"GridCoordinates"); - tree& ngons = element_section(z,cgns::NGON_n); - permute_boundary_grid_coords_at_beginning(grid_coords,vertex_permutation); - re_number_vertex_ids_in_elements(ngons,vertex_permutation); - - I vertex_partition_point = boundary_vertex_ids.size(); - save_partition_point(z,vertex_partition_point); -} - - -template auto -partition_coordinates(tree& z) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - if (!cgns::is_boundary_partitioned_zone(z)) { - auto elt_sections = get_children_by_label(z,"Elements_t"); - auto boundary_vertex_ids = get_ordered_boundary_vertex_ids(elt_sections); - permute_boundary_vertices_at_beginning(z,boundary_vertex_ids); - } -} - - -template auto -partition_elements(tree& z) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - tree& ngons = element_section(z,cgns::NGON_n); - tree& nfaces = element_section(z,cgns::NFACE_n); - auto pls = cgns::get_zone_point_lists(z,"FaceCenter"); - permute_boundary_ngons_at_beginning(ngons,nfaces,pls); -} - - -template auto -_partition_zone_with_boundary_first(tree& z) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - if (is_unstructured_zone(z)) { - partition_coordinates(z); - partition_elements(z); - } -} - -auto -partition_zone_with_boundary_first(tree& z) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - if (value(z).data_type()=="I4") return _partition_zone_with_boundary_first(z); - if (value(z).data_type()=="I8") return _partition_zone_with_boundary_first(z); - throw cgns::cgns_exception("Zone "+name(z)+" has a value of data type "+value(z).data_type()+" but it should be I4 or I8"); -} - -auto -put_boundary_first(tree& b, MPI_Comm comm) -> void { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - for (tree& z : get_children_by_label(b,"Zone_t")) { - partition_zone_with_boundary_first(z); - } -} - - -} // maia -#endif // C++>17 diff --git a/maia/__old/transform/put_boundary_first/put_boundary_first.hpp b/maia/__old/transform/put_boundary_first/put_boundary_first.hpp deleted file mode 100644 index 154d58c2..00000000 --- a/maia/__old/transform/put_boundary_first/put_boundary_first.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "std_e/future/span.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "maia/__old/transform/donated_point_lists.hpp" -#include "mpi.h" - - -namespace maia { - - -/** -This function reorders coordinates and connectivities of unstructured zones of CGNS base "b" -such that all boundary coordinates and connectivities appear first in their respective "DataArray_t" - -Preconditions: - - "b" is a CGNS base - - "Element_t" nodes of "MIXED" type are supposed to contain only interior connectivities - - 3D elements are supposed to be interior elements - - 2D elements are supposed to be boundary elements (except NGON_n elements) - - Each "Element_t" node of "NGON_n" type: - - either contains a node named "ParentElements", in which case it is possible to check if each ngon is on the boundary or interior - - or does not contain such a node and its connectivities are supposed to refer to only boundary nodes - -Postconditions: - - Only the *unstructured* zones are partitionned with boundary nodes and boundary connectivities first - (because it is impossible to partition structured zone coordinates without splitting the zone) - - "GridCoordinates" are reordered so that boundary nodes come first. The reorder is stable - - For each zone, - for each "Element_t" node of "NGON_n" type with a "ParentElements" sub-node, - the node is reordered so that boundary nodes come first - its "ElementSizeBoundary" value is set to the number or boundary connectivities - it has a "UserDefinedData_t" sub-node named ".#PartitionIndex" with an "Ordinal" which value is the number of integers representing the boundary connectivities - examples: - if all connectivities are Quad4, then ".#PartitionIndex".Ordinal = (1+4)*"ElementSizeBoundary" - if all connectivities are Tri3, then ".#PartitionIndex".Ordinal = (1+3)*"ElementSizeBoundary" - if connectivities are Tri3 and Quad4, its impossible to express only in terms of "ElementSizeBoundary" - it is the reason that this ".#PartitionIndex".Ordinal is given in the tree: its impossible to find it easily otherwise - - "PointList" nodes refering to "NGON_n" connectivities are changed to match the new NGON connectivities indexing - -Limitations: - - All PointList and PointListDonor are considered to be with GridLocation==Face (even if the GridLocation node tells the opposite: it is not checked) - - All PointLists are supposed to be disjoints between all GridConnectivity_t of a Zone_t - (reason of this supposition: it enables for faster identification) - - If a Zone A has a GridConnectivity with its PointListDonor being in Zone B, it is supposed that the inverse GridConnectivity structure is also present in Zone B, with the PointListDonor in Zone A being equal to the PointList in Zone B. - - The following nodes are deleted because their re-indexing has not been implemented: - - "Elements_t" nodes of "NFACE" type - - Sub node "ParentElementsPosition" of the ngon node - - Even if the tree is already partitionned (VertexSizeBoundary != 0), all partitionning steps are done (again). - -Complexity: - - An effort as been made to isolate all complex loop computations into well-known algorithms (find, stable_partition) - The C++ standard library implementations of find and stable_partition are O(n). - Note that it would also make sense to use std::partition instead of std::stable_partition - - Algorithms are at most linear time and space in the arrays number of elements. -*/ -auto put_boundary_first(cgns::tree& b, MPI_Comm comm) -> void; - - -} // maia diff --git a/maia/__old/transform/put_boundary_first/test/boundary_ngons_at_beginning.test.cpp b/maia/__old/transform/put_boundary_first/test/boundary_ngons_at_beginning.test.cpp deleted file mode 100644 index 3d67ee25..00000000 --- a/maia/__old/transform/put_boundary_first/test/boundary_ngons_at_beginning.test.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - -#include "maia/__old/transform/put_boundary_first/boundary_ngons_at_beginning.hpp" -#include "cpp_cgns/sids/creation.hpp" - -using namespace cgns; -using std::vector; - - -TEST_CASE("boundary_ngons_at_beginning") { - // ngons - I4 first_ngon_elt = 6; - I4 last_ngon_elt = 9; - vector ngon_cs = - { 1, 2, 3, - 10,11,12,13, - 5, 4, 3, - 1, 8, 9 }; - vector ngon_eso = {0,3,7,10,13}; - md_array parent_elts = - { {1, 4}, - {0, 8}, - {0, 0}, - {3, 1} }; - tree ngons = new_NgonElements( - "Ngons", - std::move(ngon_cs), - first_ngon_elt,last_ngon_elt - ); - emplace_child(ngons,new_DataArray("ElementStartOffset", std::move(ngon_eso))); - emplace_child(ngons,new_DataArray("ParentElements", std::move(parent_elts))); - // nfaces - I4 first_nface_elt = 10; - I4 last_nface_elt = 11; - vector nface_cs = - { 6,7,8,9, - 9,8,7 }; - vector nface_eso = {0,4,7}; - - tree nfaces = new_NfaceElements( - "Nfaces", - std::move(nface_cs), - first_nface_elt,last_nface_elt - ); - emplace_child(nfaces,new_DataArray("ElementStartOffset", std::move(nface_eso))); - // point lists - std::vector pl = {8,9}; - std::vector> pls = {std_e::make_span(pl)}; - - SUBCASE("boundary/interior_permutation") { - auto pe = ParentElements(ngons); - auto [ngon_permutation,partition_index] = maia::boundary_interior_permutation(pe); - - CHECK( partition_index == 2 ); - - vector expected_ngon_permutation = {1,2,0,3}; - CHECK( ngon_permutation == expected_ngon_permutation ); - } - - SUBCASE("permute_boundary_ngons_at_beginning") { - auto last_exterior = maia::permute_boundary_ngons_at_beginning(ngons,nfaces,pls); - - CHECK( last_exterior == 2 ); - vector expected_ngon_cs = - { 10,11,12,13, - 5, 4, 3, - 1, 2, 3, - 1, 8, 9 }; - vector expected_ngon_eso = {0,4,7,10,13}; - md_array expected_parent_elts = - { {0, 8}, - {0, 0}, - {1, 4}, - {3, 1} }; - vector expected_nface_cs = - { 8,6,7,9, - 9,7,6 }; - - CHECK( ElementConnectivity(ngons) == expected_ngon_cs ); - CHECK( ElementStartOffset(ngons) == expected_ngon_eso ); - CHECK( ParentElements(ngons) == expected_parent_elts ); - - CHECK( ElementSizeBoundary(ngons) == 2 ); - - CHECK( ElementConnectivity(nfaces) == expected_nface_cs ); - - CHECK( pl == std::vector{7,9} ); - } - - SUBCASE("partition_bnd_faces_by_number_of_vertices") { - // Not testing this, just using the result - maia::permute_boundary_ngons_at_beginning(ngons,nfaces,pls); - - // This is the one we are testing - auto last_tri_bnd = maia::partition_bnd_faces_by_number_of_vertices(ngons,nfaces,pls); - - CHECK( last_tri_bnd == 1 ); - vector expected_ngon_cs = - { 5, 4, 3, - 10,11,12,13, - 1, 2, 3, - 1, 8, 9 }; - vector expected_ngon_eso = {0,3,7,10,13}; - md_array expected_parent_elts = - { {0, 0}, - {0, 8}, - {1, 4}, - {3, 1} }; - vector expected_nface_cs = - { 8,7,6,9, - 9,6,7 }; - - CHECK( ElementConnectivity(ngons) == expected_ngon_cs ); - CHECK( ElementStartOffset(ngons) == expected_ngon_eso ); - CHECK( ParentElements(ngons) == expected_parent_elts ); - - CHECK( ElementConnectivity(nfaces) == expected_nface_cs ); - - // CHECK( pl == std::vector{6,9} ); - } -} -#endif // C++>17 diff --git a/maia/__old/transform/put_boundary_first/test/boundary_vertices.test.cpp b/maia/__old/transform/put_boundary_first/test/boundary_vertices.test.cpp deleted file mode 100644 index c7460e50..00000000 --- a/maia/__old/transform/put_boundary_first/test/boundary_vertices.test.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - -#include "maia/__old/transform/put_boundary_first/boundary_vertices.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids/elements_utils.hpp" -#include "std_e/data_structure/block_range/vblock_range.hpp" - -using namespace std; -using namespace cgns; -using namespace maia; - - -TEST_CASE("partition_with_boundary_vertices") { -// tri3 connectivities - vector tri_cs = - { 1, 2, 3, - 42,43,44, - 44,43, 3 }; - -// tet4 connectivities - vector tet_cs = - { 1, 2, 3, 4, - 42,43,44,45 }; - -// ngon connectivities - vector ngon_vtx = - { 1, 2, 3, - 10,11,12,13, - 5, 4, 3, - 1, 8, 9 }; - vector ngon_eso = {0,3,7,10,13}; - md_array parent_elts = - { {1, 4}, - {0, 8}, - {0, 0}, - {3, 1} }; - - SUBCASE("find_boundary_vertices") { - auto ngon_cs = std_e::view_as_vblock_range(ngon_vtx,ngon_eso); - auto bnd_vertices = find_boundary_vertices(ngon_cs,parent_elts); - - vector expected_boundary_vertices = { - 10,11,12,13, - 5, 4, 3 - }; - - CHECK( bnd_vertices == expected_boundary_vertices ); - } - - SUBCASE("get_ordered_boundary_vertex_ids") { - // construction of elements - tree tris = new_Elements( - "Tri", - cgns::TRI_3, - std::move(tri_cs), - 1,3 - ); - tree tets = new_Elements( - "Tet", - cgns::TETRA_4, - std::move(tet_cs), - 4,5 - ); - tree ngons = new_NgonElements( - "Ngons", - std::move(ngon_vtx), - 6,9 - ); - emplace_child(ngons,new_DataArray("ElementStartOffset", std::move(ngon_eso))); - emplace_child(ngons,new_DataArray("ParentElements", std::move(parent_elts))); - - tree_range zone_elements = {tris,tets,ngons}; - auto boundary_vertices = get_ordered_boundary_vertex_ids(zone_elements); - - vector expected_boundary_vertices = { 1, 2, 3, 4, 5, 10, 11, 12, 13, 42, 43, 44 }; - - CHECK( boundary_vertices == expected_boundary_vertices ); - } -}; -#endif // C++>17 diff --git a/maia/__old/transform/put_boundary_first/test/boundary_vertices_at_beginning.test.cpp b/maia/__old/transform/put_boundary_first/test/boundary_vertices_at_beginning.test.cpp deleted file mode 100644 index ff5cb1be..00000000 --- a/maia/__old/transform/put_boundary_first/test/boundary_vertices_at_beginning.test.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - -#include "maia/__old/transform/put_boundary_first/boundary_vertices_at_beginning.hpp" -#include "std_e/algorithm/algorithm.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids/elements_utils.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" - -using namespace std; -using namespace cgns; - -TEST_CASE("vertex_permutation_to_move_boundary_at_beginning") { - const I4 n_vtx = 10; - - std::vector boundary_vertex_ids = { 1, 2, 5, 9 }; - auto vertex_permutation = maia::vertex_permutation_to_move_boundary_at_beginning(n_vtx,boundary_vertex_ids); - - int partition_point = 4; - std::vector boundary_vertex_positions = { 0, 1, 4, 8 }; // == boundary_vertex_ids - 1 - std::vector interior_vertex_positions = { 2, 3, 5, 6, 7, 9 }; // remaining positions - - REQUIRE( vertex_permutation.size() == n_vtx ); - for (int i=0; i my_vertex_permutation = { 0,1, 5,6,7, 2,3,4, 8,9 }; - // |_| |___| |___| |_| - // still ^ ^ still - // same |_________| same - // position rotated position - // - // so { 1,2, 3,4,5, 6,7,8, 9,10 }; - // should become { 1,2, 6,7,8, 3,4,5 9,10 }; - - SUBCASE("tris") { - std::vector tri_cs = - { 1, 2, 3, - 1, 3, 4, - 4, 5, 9 }; - tree tris = new_Elements( - "Tri", - cgns::TRI_3, - std::move(tri_cs), - 1,3 - ); - maia::re_number_vertex_ids_in_elements(tris,my_vertex_permutation); - - auto tri_view = ElementConnectivity(tris); - - CHECK_EQ( 1 , tri_view[0] ); - CHECK_EQ( 2 , tri_view[1] ); - CHECK_EQ( 6 , tri_view[2] ); - - CHECK_EQ( 1 , tri_view[3] ); - CHECK_EQ( 6 , tri_view[4] ); - CHECK_EQ( 7 , tri_view[5] ); - - CHECK_EQ( 7 , tri_view[6] ); - CHECK_EQ( 8 , tri_view[7] ); - CHECK_EQ( 9 , tri_view[8] ); - } - - SUBCASE("ngons") { - std::vector ngon_cs = - { 1, 2, 3, - 4, 5, 6,10, - 9, 8, 7, - 1, 8, 9 }; - std::vector ngon_eso = {0,3,7,10,13}; - - tree ngons = new_NgonElements( - "Ngons", - std::move(ngon_cs), - 6,9 - ); - emplace_child(ngons,new_DataArray("ElementStartOffset", node_value(std::move(ngon_eso)))); - - maia::re_number_vertex_ids_in_elements(ngons,my_vertex_permutation); - - std::vector expected_ngon_cs = - { 1, 2, 6, - 7, 8, 3,10, - 9, 5, 4, - 1, 5, 9 }; - CHECK( ElementConnectivity(ngons) == expected_ngon_cs ); - } -} -#endif // C++>17 diff --git a/maia/__old/transform/put_boundary_first/test/put_boundary_first.test.cpp b/maia/__old/transform/put_boundary_first/test/put_boundary_first.test.cpp deleted file mode 100644 index d14456e1..00000000 --- a/maia/__old/transform/put_boundary_first/test/put_boundary_first.test.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - -#include "maia/__old/utils/cgns_tree_examples/unstructured_base.hpp" -#include "maia/__old/transform/put_boundary_first/put_boundary_first.hpp" -#include "std_e/algorithm/algorithm.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "cpp_cgns/sids/Hierarchical_Structures.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" - -using namespace cgns; - -TEST_CASE("put_boundary_first, with 2 zones") { - tree base = create_unstructured_base(); - - maia::put_boundary_first(base,MPI_COMM_SELF); - - // zones - tree& z0 = get_child_by_name(base,"Zone0"); - tree& z1 = get_child_by_name(base,"Zone1"); - - // sizes - CHECK( VertexSize_U(z0) == 24 ); - CHECK( CellSize_U(z0) == 6 ); - CHECK( VertexBoundarySize_U(z0) == 20 ); - - // coordinates - auto z0_coordX = get_node_value_by_matching(z0,"GridCoordinates/CoordinateX"); - auto z0_coordY = get_node_value_by_matching(z0,"GridCoordinates/CoordinateY"); - auto z0_coordZ = get_node_value_by_matching(z0,"GridCoordinates/CoordinateZ"); - std::vector> expected_interior_elts_coords = { - {1.,1.,0}, // node 5 of simple_meshes.h - {2.,1.,0}, // node 6 - {1.,1.,1}, // node 17 - {2.,1.,1}, // node 18 - }; - std::array interior_coord_0 = {z0_coordX[20],z0_coordY[20],z0_coordZ[20]}; - CHECK(std_e::contains(expected_interior_elts_coords,interior_coord_0)); - - std::array interior_coord_1 = {z0_coordX[21],z0_coordY[21],z0_coordZ[21]}; - CHECK(std_e::contains(expected_interior_elts_coords,interior_coord_1)); - - std::array interior_coord_2 = {z0_coordX[22],z0_coordY[22],z0_coordZ[22]}; - CHECK(std_e::contains(expected_interior_elts_coords,interior_coord_2)); - - std::array interior_coord_3 = {z0_coordX[23],z0_coordY[23],z0_coordZ[23]}; - CHECK(std_e::contains(expected_interior_elts_coords,interior_coord_3)); - - - // bcs - tree& z0_inflow_pl_node = get_node_by_matching(z0,"ZoneBC/Inlet/PointList"); - auto z0_inflow_pl = get_value(z0_inflow_pl_node); - REQUIRE( z0_inflow_pl.size() == 2 ); - CHECK( z0_inflow_pl[0] == 1 ); - CHECK( z0_inflow_pl[1] == 2 ); - - // gcs - tree& z0_grid_connec_pl_node = get_node_by_matching(z0,"ZoneGridConnectivity/MixingPlane/PointList"); - auto z0_grid_connec_pl = get_value(z0_grid_connec_pl_node); - tree& z0_grid_connec_pld_node = get_node_by_matching(z0,"ZoneGridConnectivity/MixingPlane/PointListDonor"); - auto z0_grid_connec_pld = get_value(z0_grid_connec_pld_node); - REQUIRE( z0_grid_connec_pl.size() == 1 ); - CHECK( z0_grid_connec_pl[0] == 3 ); - REQUIRE( z0_grid_connec_pld.size() == 1 ); - CHECK( z0_grid_connec_pld[0] == 1 ); - /// since there is no renumbering, in this case, on z0, - /// we check the renumbering was done by looking z1 - tree& z1_grid_connec_pld_node = get_node_by_matching(z1,"ZoneGridConnectivity/MixingPlane/PointListDonor"); - auto z1_grid_connec_pld = get_value(z1_grid_connec_pld_node); - REQUIRE( z1_grid_connec_pld.size() == 1 ); - //CHECK( z1_grid_connec_pld[0] == 3 ); // TODO fails gcc 10 - - // elements - tree& z0_ngon = get_child_by_name(z0,"Ngons"); - auto z0_ngon_elt_range = ElementRange(z0_ngon); - CHECK( z0_ngon_elt_range[0] == 1 ); - CHECK( z0_ngon_elt_range[1] == 8 + 9 + 12 ); - auto z0_nb_boundary_ngons = ElementSizeBoundary(z0_ngon); - CHECK( z0_nb_boundary_ngons == 3+2+3+2 ); - auto z0_ngon_elt_connect = ElementConnectivity(z0_ngon); - - REQUIRE( z0_ngon_elt_connect.size() == (8 + 9 + 12)*4 ); - CHECK( z0_ngon_elt_connect[0] == 1 ); - CHECK( z0_ngon_elt_connect[1] == 5 ); - CHECK( z0_ngon_elt_connect[2] == 17 ); - CHECK( z0_ngon_elt_connect[3] == 13 ); - int beginning_last_ngon = (8+9+12-1)*4; - CHECK( z0_ngon_elt_connect[beginning_last_ngon+0] == 21 ); // {21 20 6 7} are the new cgns vertex ids - CHECK( z0_ngon_elt_connect[beginning_last_ngon+1] == 20 ); // of face {18 19 23 22} in simple_meshes.h, - CHECK( z0_ngon_elt_connect[beginning_last_ngon+2] == 6 ); // and this face is an interior face (because - CHECK( z0_ngon_elt_connect[beginning_last_ngon+3] == 7 ); // in this test all k-faces are considered interior) - - auto z0_ngon_parent_elts = ParentElements(z0_ngon); - REQUIRE( z0_ngon_parent_elts.size() == (8 + 9 + 12)*2 ); - CHECK( z0_ngon_parent_elts(0,0) == 0 ); CHECK( z0_ngon_parent_elts(0,1) == 1 ); - CHECK( z0_ngon_parent_elts(1,0) == 0 ); CHECK( z0_ngon_parent_elts(1,1) == 4 ); - CHECK( z0_ngon_parent_elts(2,0) == 3 ); CHECK( z0_ngon_parent_elts(2,1) == 0 ); - CHECK( z0_ngon_parent_elts(3,0) == 6 ); CHECK( z0_ngon_parent_elts(3,1) == 0 ); -} -#endif // C++>17 diff --git a/maia/__old/transform/put_boundary_first/test/test/permute.test.hpp b/maia/__old/transform/put_boundary_first/test/test/permute.test.hpp deleted file mode 100644 index d4208652..00000000 --- a/maia/__old/transform/put_boundary_first/test/test/permute.test.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - -#include "maia/__old/transform/put_boundary_first/permute.hpp" -#include "cpp_cgns/sids/creation.hpp" - -using namespace cgns; -using std::vector; - - -TEST_CASE("boundary_ngons_at_beginning") { - md_array parent_elts = - { {1, 4}, - {0, 8}, - {0, 0}, - {3, 1} }; - auto pe_view = make_view(parent_elts); - vector my_permutation = {1,2, 0,3}; - - maia::permute_parent_elements(pe_view,my_permutation); - - md_array expected_parent_elts = - { {0, 8}, - {0, 0}, - {1, 4}, - {3, 1} }; - CHECK( parent_elts == expected_parent_elts ); -} -#endif // C++>17 diff --git a/maia/__old/transform/remove_ghost_info.cpp b/maia/__old/transform/remove_ghost_info.cpp deleted file mode 100644 index 21b306c6..00000000 --- a/maia/__old/transform/remove_ghost_info.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/transform/remove_ghost_info.hpp" - -#include "maia/__old/transform/base_renumbering.hpp" -#include "cpp_cgns/sids/utils.hpp" -#include "cpp_cgns/sids/elements_utils.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" -#include "cpp_cgns/sids/Building_Block_Structure_Definitions.hpp" -#include "std_e/algorithm/iota.hpp" -#include "std_e/algorithm/algorithm.hpp" -#include "maia/__old/transform/renumber_point_lists.hpp" - -using cgns::tree; -using cgns::I4; -using cgns::I8; -using namespace cgns; // TODO - -namespace maia { - -auto -remove_ghost_info(tree& b, MPI_Comm comm) -> void { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - apply_base_renumbering(b,remove_ghost_info_from_zone,comm); - - for (tree& z : get_children_by_label(b,"Zone_t")) { - rm_invalid_ids_in_point_lists(z,"FaceCenter"); // For BC - rm_invalid_ids_in_point_lists_with_donors(z,"Vertex"); // For GC - rm_grid_connectivities(z,"FaceCenter"); - rm_grid_connectivities(z,"CellCenter"); - } - - symmetrize_grid_connectivities(b,comm); -} - - -template auto -nb_elements(const tree& elt_pool) -> I { - auto elt_range = ElementRange(elt_pool); - return elt_range[1]-elt_range[0]+1; // CGNS ranges are closed -} -template auto -nb_ghost_elements(const tree& elt_pool) -> I { - if (has_child_of_name(elt_pool,"Rind")) { - auto elt_rind = Rind(elt_pool); - STD_E_ASSERT(elt_rind[0]==0); - return elt_rind[1]; - } else { - return 0; - } -} -template auto -nb_owned_elements(const tree& elt_pool) -> I { - return nb_elements(elt_pool) - nb_ghost_elements(elt_pool); -} - -// TODO BC, renum connec vertices after -/** - * \brief remove all ghost info, but keeps the GridCOnnectivity 1-to-1 on nodes - * \param [inout] z: the cgns zone - * \param [in] plds: PointListDonors from other zones pointing to zone "z" - * \pre ghost nodes/elements are given by "Rind" nodes - * \pre ghost nodes/elements are given their "owner" zone and id by GridConnectivities - * - * \details - * 1. remove ghost elements (rind + grid connectivity) - * 2. renumber elts in point lists - * 3. remove ghost nodes (rind) - * 4. only keep those used in conncectivities - * 5. renumber nodes in GridCoordinates, PointList, PointListDonor from other zones, Elements - * 6. delete invalid PointList -*/ -auto -remove_ghost_info_from_zone(tree& z, donated_point_lists& plds) -> void { - tree_range elt_pools = get_children_by_label(z,"Elements_t"); - std::sort(begin(elt_pools),end(elt_pools),compare_by_range); - STD_E_ASSERT(cgns::elts_ranges_are_contiguous(elt_pools)); - int nb_elt_pools = elt_pools.size(); - - // 0. compute elt permutation - I4 elt_first_id = ElementRange(elt_pools[0])[0]; - std::vector permutation; - std_e::interval_vector intervals = {0}; - I4 nb_owned_cells = 0; - for (const tree& elt_pool: elt_pools) { - ElementType_t elt_type = element_type(elt_pool); - I4 nb_owned_elts = nb_owned_elements(elt_pool); - I4 nb_ghost_elts = nb_ghost_elements(elt_pool); - - if (element_dimension(elt_type)==3) { - nb_owned_cells += nb_owned_elts; - } - - std_e::iota_n(std::back_inserter(permutation), nb_owned_elts, intervals.back()); - intervals.push_back_length(nb_owned_elts); - std::fill_n(std::back_inserter(permutation), nb_ghost_elts, -1-1); // UGLY: -offset (here: -1) because just after, offset_permutation does +offset - } - std_e::offset_permutation perm(elt_first_id,1,permutation); - - // 1. renum pl BC - renumber_point_lists(z,perm,"FaceCenter"); - //renumber_point_lists(z,perm,"CellCenter"); - //renumber_point_lists_donated(plds,perm,"FaceCenter"); - //renumber_point_lists_donated(plds,perm,"CellCenter"); - - // 2. rm ghost cells - CellSize_U(z) = nb_owned_cells; - for (int i=0; i(elt_pool); - elt_range[0] = intervals[i]+1; - elt_range[1] = intervals[i+1]; - - auto elt_type = element_type(elt_pool); - tree& elt_connec = get_child_by_name(elt_pool,"ElementConnectivity"); - // TODO once pybind11: do not allocate/copy/del, only resize - //elt_connec.value.dims[0] = intervals.length(i)*number_of_vertices(elt_type); - // del old { - int new_connec_size = intervals.length(i)*number_of_vertices(elt_type); - auto old_connec_val = get_value(elt_connec); - std::vector new_connec_val(new_connec_size); - for (int i=0; i(z); - std::vector nodes2(old_nb_nodes,-1); - for (const tree& elt_pool: elt_pools) { - auto cs = ElementConnectivity(elt_pool); - for (I4 c : cs) { - nodes2[c-1] = 0; - } - } - // TODO remplate by filter(!=-1) | iota - I4 cnt = 0; - for (I4& n : nodes2) { - if (n!=-1) { - n = cnt++; - } - } - // 4.1. } - - int nb_nodes2 = cnt; - - // 5. delete unused nodes - VertexSize_U(z) = nb_nodes2; - - tree& coords = get_child_by_name(z,"GridCoordinates"); - rm_child_by_name(coords,"Rind"); - - /// 5.0. renumber GridCoordinates - for (tree& coord : get_children_by_label(coords,"DataArray_t")) { - // TODO once pybind11: do not allocate/copy/del, only resize - auto old_coord_val = get_value(coord); - std::vector new_coord_val(nb_nodes2); - for (I4 i=0; i(elt_pool,"ElementConnectivity"); - for (I4& node : connec) { - node = nodes2[node-1]+1; // TODO invert in offset_permutation - } - } - - /// 5.2. renumber pl - std_e::offset_permutation node_perm2(1,std::move(nodes2)); // CGNS nodes indexed at 1 - renumber_point_lists2(z,node_perm2,"Vertex"); - renumber_point_lists_donated(plds,node_perm2,"Vertex"); -} - -} // maia -#endif // C++>17 diff --git a/maia/__old/transform/remove_ghost_info.hpp b/maia/__old/transform/remove_ghost_info.hpp deleted file mode 100644 index 3f6c8755..00000000 --- a/maia/__old/transform/remove_ghost_info.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "cpp_cgns/cgns.hpp" -#include "mpi.h" -#include "maia/__old/transform/donated_point_lists.hpp" - -namespace maia { - -auto remove_ghost_info(cgns::tree& b, MPI_Comm comm) -> void; - -auto remove_ghost_info_from_zone(cgns::tree& z, donated_point_lists& plds) -> void; - -} // maia diff --git a/maia/__old/transform/renumber_point_lists.cpp b/maia/__old/transform/renumber_point_lists.cpp deleted file mode 100644 index e384c1bf..00000000 --- a/maia/__old/transform/renumber_point_lists.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/transform/renumber_point_lists.hpp" - -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids/utils.hpp" -#include "std_e/future/contract.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "cpp_cgns/sids/Building_Block_Structure_Definitions.hpp" -#include // TODO - - -using cgns::tree; -using cgns::node_value; -using cgns::I4; -using cgns::I8; - - -namespace maia { - - -template auto -renumber_point_list(std_e::span pl, const std_e::offset_permutation& permutation) -> void { - // Precondition: permutation is an index permutation (i.e. sort(permutation) == integer_range(permutation.size())) - std_e::apply(permutation,pl); -} -template auto -renumber_point_lists(const std::vector>& pls, const std_e::offset_permutation& permutation) -> void { - for (auto pl : pls) { - renumber_point_list(pl,permutation); - } -} - -auto -renumber_point_list2(std_e::span pl, const std_e::offset_permutation& permutation) -> void { - // Precondition: permutation is an index permutation (i.e. sort(permutation) == integer_range(permutation.size())) - for (auto& i : pl) { - //STD_E_ASSERT(i>0 && i<=permutation.perm.size()); - i = permutation(i); - if (i==0) i=-1; // TODO FIXME this is because offset_permutation also offsets -1 rather than not messing with this special value - //STD_E_ASSERT(i>0 && i<=permutation.perm.size()); - } -} - -template auto -for_each_point_list(tree& z, const std::string& grid_location, Fun f) { - STD_E_ASSERT(label(z)=="Zone_t"); - std::vector search_gen_paths = {"ZoneBC/BC_t","ZoneGridConnectivity/GridConnectivity_t"}; // TODO and other places! - for (tree& bc : get_nodes_by_matching(z,search_gen_paths)) { - if (GridLocation(bc)==grid_location) { - f(value(get_child_by_name(bc,"PointList"))); - } - } -} - -auto -is_bc_gc_with_empty_point_list(const tree& t) -> bool { - return - (label(t)=="BC_t" || label(t)=="GridConnectivity_t") - && value(get_child_by_name(t,"PointList")).extent(1)==0; -} -auto -remove_if_empty_point_list(tree& z) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - // TODO this is ugly, think of something better - std::vector search_node_names = {"ZoneBC","ZoneGridConnectivity"}; - for (const auto& search_node_name : search_node_names) { - if (has_child_of_name(z,search_node_name)) { - tree& search_node = get_child_by_name(z,search_node_name); - rm_children_by_predicate(search_node, is_bc_gc_with_empty_point_list); - } - } -} - -template auto -renumber_point_lists(tree& z, const std_e::offset_permutation& permutation, const std::string& grid_location) -> void { - auto f = [&permutation](auto& pl){ renumber_point_list(cgns::view_as_span(pl),permutation); }; - for_each_point_list(z,grid_location,f); -} -auto -renumber_point_lists2(tree& z, const std_e::offset_permutation& permutation, const std::string& grid_location) -> void { - auto f = [&permutation](auto& pl){ renumber_point_list2(cgns::view_as_span(pl),permutation); }; - for_each_point_list(z,grid_location,f); -} - -auto -renumber_point_lists_donated(donated_point_lists& plds, const std_e::offset_permutation& permutation, const std::string& grid_location) -> void { - // TODO replace by multi_range iteration - for (auto& pld : plds) { - if (to_string(pld.loc)==grid_location) { - renumber_point_list2(pld.pl,permutation); - } - } -} - - -auto -rm_invalid_ids_in_point_list(node_value& pl) -> void { - auto old_pl_val = view_as_span(pl); - std::vector new_pl_val; - std::copy_if(begin(old_pl_val),end(old_pl_val),std::back_inserter(new_pl_val),[](I4 i){ return i!=-1; }); - pl = node_value(std::move(new_pl_val)); -} -auto -rm_invalid_ids_in_point_lists(tree& z, const std::string& grid_location) -> void { - for_each_point_list(z,grid_location,rm_invalid_ids_in_point_list); - remove_if_empty_point_list(z); -} -auto -rm_invalid_ids_in_point_lists_with_donors(tree& z, const std::string& grid_location) -> void { - STD_E_ASSERT(label(z)=="Zone_t"); - for (tree& bc : get_nodes_by_matching(z,"ZoneGridConnectivity/GridConnectivity_t")) { - if (GridLocation(bc)==grid_location) { - node_value& pl = value(get_child_by_name(bc,"PointList")); - node_value& pld = value(get_child_by_name(bc,"PointListDonor")); - auto old_pl_val = view_as_span(pl); - auto old_pld_val = view_as_span(pld); - std::vector new_pl_val; - std::vector new_pld_val; - int old_nb_pl = old_pl_val.size(); - for (int i=0; i void { - STD_E_ASSERT(label(z)=="Zone_t"); - tree& zgc = get_child_by_name(z,"ZoneGridConnectivity"); - rm_children_by_predicate(zgc, [&](const tree& n){ return label(n)=="GridConnectivity_t" && GridLocation(n)==grid_location; }); -} - - -template auto renumber_point_lists(tree& z, const std_e::offset_permutation& permutation, const std::string& grid_location) -> void; -template auto renumber_point_lists(tree& z, const std_e::offset_permutation& permutation, const std::string& grid_location) -> void; -template auto renumber_point_lists(const std::vector>& pls, const std_e::offset_permutation& permutation) -> void; -template auto renumber_point_lists(const std::vector>& pls, const std_e::offset_permutation& permutation) -> void; - -} // maia -#endif // C++>17 diff --git a/maia/__old/transform/renumber_point_lists.hpp b/maia/__old/transform/renumber_point_lists.hpp deleted file mode 100644 index 9890bf99..00000000 --- a/maia/__old/transform/renumber_point_lists.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "std_e/algorithm/id_permutations.hpp" -#include "maia/__old/transform/donated_point_lists.hpp" - - -namespace maia { - -template auto -renumber_point_lists(const std::vector>& pls, const std_e::offset_permutation& permutation) -> void; - -template -auto renumber_point_lists(cgns::tree& z, const std_e::offset_permutation& permutation, const std::string& grid_location) -> void; - -auto renumber_point_lists2(cgns::tree& z, const std_e::offset_permutation& permutation, const std::string& grid_location) -> void; // TODO DEL -auto renumber_point_lists_donated(donated_point_lists& plds, const std_e::offset_permutation& permutation, const std::string& grid_location) -> void; - -auto -rm_invalid_ids_in_point_lists(cgns::tree& z, const std::string& grid_location) -> void; -auto -rm_invalid_ids_in_point_lists_with_donors(cgns::tree& z, const std::string& grid_location) -> void; -auto -rm_grid_connectivities(cgns::tree& z, const std::string& grid_location) -> void; - -} // maia diff --git a/maia/__old/transform/test/base_renumbering.test.cpp b/maia/__old/transform/test/base_renumbering.test.cpp deleted file mode 100644 index 26f44fb4..00000000 --- a/maia/__old/transform/test/base_renumbering.test.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#if __cplusplus > 201703L -#include "doctest/extensions/doctest_mpi.h" - -#include "maia/__old/transform/base_renumbering.hpp" -#include "maia/__old/transform/put_boundary_first/put_boundary_first.hpp" -#include "maia/__old/utils/cgns_tree_examples/unstructured_base.hpp" -#include "std_e/algorithm/algorithm.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "cpp_cgns/sids/Hierarchical_Structures.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" - -using namespace cgns; - -TEST_CASE("base renumbering") { - tree base = create_unstructured_base(); - maia::put_boundary_first(base,MPI_COMM_SELF); - - // zone 0 - tree& z0 = get_child_by_name(base,"Zone0"); - - // sizes - CHECK( VertexSize_U(z0) == 24 ); - CHECK( CellSize_U(z0) == 6 ); - CHECK( VertexBoundarySize_U(z0) == 20 ); - - // coordinates - tree& z0_coordX_node = get_node_by_matching(z0,"GridCoordinates/CoordinateX"); - tree& z0_coordY_node = get_node_by_matching(z0,"GridCoordinates/CoordinateY"); - tree& z0_coordZ_node = get_node_by_matching(z0,"GridCoordinates/CoordinateZ"); - auto z0_coordX = view_as_span(value(z0_coordX_node)); - auto z0_coordY = view_as_span(value(z0_coordY_node)); - auto z0_coordZ = view_as_span(value(z0_coordZ_node)); - std::vector> expected_interior_elts_coords = { - {1.,1.,0}, // node 5 of simple_meshes.h - {2.,1.,0}, // node 6 - {1.,1.,1}, // node 17 - {2.,1.,1}, // node 18 - }; - std::array interior_coord_0 = {z0_coordX[20],z0_coordY[20],z0_coordZ[20]}; - CHECK(std_e::contains(expected_interior_elts_coords,interior_coord_0)); - - std::array interior_coord_1 = {z0_coordX[21],z0_coordY[21],z0_coordZ[21]}; - CHECK(std_e::contains(expected_interior_elts_coords,interior_coord_1)); - - std::array interior_coord_2 = {z0_coordX[22],z0_coordY[22],z0_coordZ[22]}; - CHECK(std_e::contains(expected_interior_elts_coords,interior_coord_2)); - - std::array interior_coord_3 = {z0_coordX[23],z0_coordY[23],z0_coordZ[23]}; - CHECK(std_e::contains(expected_interior_elts_coords,interior_coord_3)); - - - // bcs - tree& z0_inflow_pl_node = get_node_by_matching(z0,"ZoneBC/Inlet/PointList"); - auto z0_inflow_pl = view_as_span(value(z0_inflow_pl_node)); - REQUIRE( z0_inflow_pl.size() == 2 ); - CHECK( z0_inflow_pl[0] == 1 ); - CHECK( z0_inflow_pl[1] == 2 ); - - // gcs - tree& z0_grid_connec_pl_node = get_node_by_matching(z0,"ZoneGridConnectivity/MixingPlane/PointList"); - auto z0_grid_connec_pl = view_as_span(value(z0_grid_connec_pl_node)); - tree& z0_grid_connec_pld_node = get_node_by_matching(z0,"ZoneGridConnectivity/MixingPlane/PointListDonor"); - auto z0_grid_connec_pld = view_as_span(value(z0_grid_connec_pld_node)); - REQUIRE( z0_grid_connec_pl.size() == 1 ); - CHECK( z0_grid_connec_pl[0] == 3 ); - REQUIRE( z0_grid_connec_pld.size() == 1 ); - CHECK( z0_grid_connec_pld[0] == 1 ); - - // elements - tree& z0_ngon = get_child_by_name(z0,"Ngons"); - auto z0_ngon_elt_range = ElementRange(z0_ngon); - CHECK( z0_ngon_elt_range[0] == 1 ); - CHECK( z0_ngon_elt_range[1] == 8 + 9 + 12 ); - auto z0_nb_boundary_ngons = ElementSizeBoundary(z0_ngon); - CHECK( z0_nb_boundary_ngons == 3+2+3+2 ); - auto z0_ngon_elt_connect = ElementConnectivity(z0_ngon); - - REQUIRE( z0_ngon_elt_connect.size() == (8 + 9 + 12)*4 ); - CHECK( z0_ngon_elt_connect[0] == 1 ); - CHECK( z0_ngon_elt_connect[1] == 5 ); - CHECK( z0_ngon_elt_connect[2] == 17 ); - CHECK( z0_ngon_elt_connect[3] == 13 ); - int beginning_last_ngon = (8+9+12-1)*4; - CHECK( z0_ngon_elt_connect[beginning_last_ngon+0] == 21 ); // {21 20 6 7} are the new cgns vertex ids - CHECK( z0_ngon_elt_connect[beginning_last_ngon+1] == 20 ); // of face {18 19 23 22} in simple_meshes.h, - CHECK( z0_ngon_elt_connect[beginning_last_ngon+2] == 6 ); // and this face is an interior face (because - CHECK( z0_ngon_elt_connect[beginning_last_ngon+3] == 7 ); // in this test all k-faces are considered interior) - - auto z0_ngon_parent_elts = ParentElements(z0_ngon); - REQUIRE( z0_ngon_parent_elts.size() == (8 + 9 + 12)*2 ); - CHECK( z0_ngon_parent_elts(0,0) == 0 ); CHECK( z0_ngon_parent_elts(0,1) == 1 ); - CHECK( z0_ngon_parent_elts(1,0) == 0 ); CHECK( z0_ngon_parent_elts(1,1) == 4 ); - CHECK( z0_ngon_parent_elts(2,0) == 3 ); CHECK( z0_ngon_parent_elts(2,1) == 0 ); - CHECK( z0_ngon_parent_elts(3,0) == 6 ); CHECK( z0_ngon_parent_elts(3,1) == 0 ); -} -#endif // C++>17 diff --git a/maia/__old/transform/test/connectivity_range.test.cpp b/maia/__old/transform/test/connectivity_range.test.cpp deleted file mode 100644 index 55b4de25..00000000 --- a/maia/__old/transform/test/connectivity_range.test.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest_pybind.hpp" - -#include "cpp_cgns/sids/creation.hpp" -#include "maia/__old/transform/connectivity_range.hpp" - -using cgns::I4; -using cgns::md_array; -using cgns::tree; -using std::vector; - -TEST_CASE("connectivity_range") { - I4 first_ngon_elt = 6; - I4 last_ngon_elt = 9; - vector ngon_cs = - { 1, 2, 3, - 10,11,12,13, - 5, 4, 3, - 1, 8, 9 }; - vector ngon_eso = {0,3,7,10,13}; - md_array parent_elts = - { {1, 4}, - {0, 8}, - {0, 0}, - {3, 1} }; - - tree ngons = cgns::new_NgonElements( - "Ngons", - std::move(ngon_cs), - first_ngon_elt,last_ngon_elt - ); - emplace_child(ngons,cgns::new_DataArray("ElementStartOffset", std::move(ngon_eso))); - emplace_child(ngons,cgns::new_DataArray("ParentElements", std::move(parent_elts))); - - SUBCASE("make_connectivity_range") { - auto face_vtx = maia::make_connectivity_range(ngons); - CHECK( face_vtx.size() == 4 ); - CHECK( face_vtx[0] == vector{1,2,3} ); - CHECK( face_vtx[1] == vector{10,11,12,13} ); - CHECK( face_vtx[2] == vector{5,4,3} ); - CHECK( face_vtx[3] == vector{1,8,9} ); - } - - SUBCASE("make_connectivity_subrange") { - auto face_vtx = maia::make_connectivity_subrange(ngons,1,3); - CHECK( face_vtx.size() == 2 ); - CHECK( face_vtx[0] == vector{10,11,12,13} ); - CHECK( face_vtx[1] == vector{5,4,3} ); - } -} -#endif // C++>17 diff --git a/maia/__old/utils/cgns_tree_examples/base_two_ranks.cpp b/maia/__old/utils/cgns_tree_examples/base_two_ranks.cpp deleted file mode 100644 index 947ef013..00000000 --- a/maia/__old/utils/cgns_tree_examples/base_two_ranks.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/utils/cgns_tree_examples/base_two_ranks.hpp" -#include "cpp_cgns/sids/creation.hpp" - -using namespace cgns; - -namespace example { - -auto -create_base_two_ranks(int mpi_rank) -> tree { - STD_E_ASSERT(mpi_rank==0 || mpi_rank==1); - tree b = new_CGNSBase("Base",3,3); - -/* Case (note that GridConnectivities are *not* symmetric: - proc 0 - /Base/Zone0 - /Base/Zone0/ZGC/j0 --> Zone0 - /Base/Zone0/ZGC/j1 --> Zone1 - /Base/Zone3 - /Base/Zone3/ZGC/j2 --> Zone1 - /Base/Zone3/ZGC/j3 --> Zone1 - - proc 1 - /Base/Zone1 - /Base/Zone1/ZGC/j4 --> Zone1 - /Base/Zone1/ZGC/j5 --> Zone0 - /Base/Zone2 - /Base/Zone2/ZGC/j6 --> Zone3 -*/ - - if (mpi_rank == 0) { - // Zone0 - tree z0 = new_UnstructuredZone("Zone0"); - - tree pld00 = new_PointList("PointListDonor",std::vector{1,2,3}); - tree gc00 = new_GridConnectivity("Join0","Zone0","FaceCenter","Abutting1to1"); - emplace_child(gc00,std::move(pld00)); - - tree pld01 = new_PointList("PointListDonor",std::vector{11,12,13,14}); - tree gc01 = new_GridConnectivity("Join1","Zone1","FaceCenter","Abutting1to1"); - emplace_child(gc01,std::move(pld01)); - - tree zone_gc0 = new_ZoneGridConnectivity(); - emplace_child(zone_gc0,std::move(gc00)); - emplace_child(zone_gc0,std::move(gc01)); - emplace_child(z0,std::move(zone_gc0)); - - emplace_child(b,std::move(z0)); - // Zone3 - tree z3 = new_UnstructuredZone("Zone3"); - - tree pld31a = new_PointList("PointListDonor",std::vector{15}); - tree gc31a = new_GridConnectivity("Join2","Zone1","Vertex","Abutting1to1"); - emplace_child(gc31a,std::move(pld31a)); - - tree pld31b = new_PointList("PointListDonor",std::vector{16,17}); - tree gc31b = new_GridConnectivity("Join3","Zone1","Vertex","Abutting1to1"); - emplace_child(gc31b,std::move(pld31b)); - - tree zone_gc3 = new_ZoneGridConnectivity(); - emplace_child(zone_gc3,std::move(gc31a)); - emplace_child(zone_gc3,std::move(gc31b)); - emplace_child(z3,std::move(zone_gc3)); - - emplace_child(b,std::move(z3)); - } else { - STD_E_ASSERT(mpi_rank == 1); - // Zone1 - tree z1 = new_UnstructuredZone("Zone1"); - - tree pld11 = new_PointList("PointListDonor",std::vector{101,102,103,104}); - tree gc11 = new_GridConnectivity("Join4","Zone1","CellCenter","Abutting1to1"); - emplace_child(gc11,std::move(pld11)); - - tree pld10 = new_PointList("PointListDonor",std::vector{111,112}); - tree gc10 = new_GridConnectivity("Join5","Zone0","Vertex","Abutting1to1"); - emplace_child(gc10,std::move(pld10)); - - tree zone_gc1 = new_ZoneGridConnectivity(); - emplace_child(zone_gc1,std::move(gc11)); - emplace_child(zone_gc1,std::move(gc10)); - emplace_child(z1,std::move(zone_gc1)); - - emplace_child(b,std::move(z1)); - - // Zone2 - tree z2 = new_UnstructuredZone("Zone2"); - - tree pld21 = new_PointList("PointListDonor",std::vector{136,137}); - tree gc21 = new_GridConnectivity("Join6","Zone3","CellCenter","Abutting1to1"); - emplace_child(gc21,std::move(pld21)); - - tree zone_gc2 = new_ZoneGridConnectivity(); - emplace_child(zone_gc2,std::move(gc21)); - emplace_child(z2,std::move(zone_gc2)); - - emplace_child(b,std::move(z2)); - } - return b; -} - -} // example -#endif // C++>17 diff --git a/maia/__old/utils/cgns_tree_examples/base_two_ranks.hpp b/maia/__old/utils/cgns_tree_examples/base_two_ranks.hpp deleted file mode 100644 index 1758f9a8..00000000 --- a/maia/__old/utils/cgns_tree_examples/base_two_ranks.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" - - -namespace example { - -auto -create_base_two_ranks(int mpi_rank) -> cgns::tree; - -} // example diff --git a/maia/__old/utils/cgns_tree_examples/simple_meshes.hpp b/maia/__old/utils/cgns_tree_examples/simple_meshes.hpp deleted file mode 100644 index 1ddc0b5f..00000000 --- a/maia/__old/utils/cgns_tree_examples/simple_meshes.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once - -// TODO put this in doc -/* Maillage "one_quad" -Le maillage utilisé est le suivant: - _ y j - /_/| |_ x |_ i - |_|/ / / - z k -Les ids des noeuds sont les suivants: (ordre fortran, indexé à 1): - ________________ - /3 /4 - / /| - /__|____________ / | - 7| 8| | - | | | | - | | | - | | | | - | _ _ _ _ _ _ | _| - | 1 | /2 - | / | / - |_______________|/ - 5 6 - -Le noeud 1 est en (3.,0.,0.) et le côté du cube est 1. -*/ - -/* Maillage "six_quads" -Le maillage utilisé est le suivant: - _ _ _ - /_/_/_/| y j - |_|_|_|/| |_ x |_ i - |_|_|_|/ / / - z k - -Les ids des noeuds sont les suivants (ordre fortran, indexé à 1): - ________________________________________________ - /9 /10 /11 /12 - / / / /| - /__|____________/__|____________/__|____________ / | - 21| 22| 23| 24| | - | | | | | | | | - | /4/ | /5/ | /6/ | | - | | | | | | | | - | _ _ _ _ _ _ | _ _ _ _ _ _ | _ _ _ _ _ _ | _| - | 5 | 6 | 7 | /8 - | / | / | / | /| - |__|____________|__|____________|__|____________|/ | - 17| 18| 19| 20| | - | | | | | | | | - | /1/ | /2/ | /3/ | | - | | | | | | | | - | _ _ _ _ _ _ | _ _ _ _ _ _ | _ _ _ _ _ _ | _| - | 1 | 2 | 3 | /4 - | / | / | / | / - |_______________|_______________|_______________|/ - 13 14 15 16 - - -faces: -i - 2 4 6 8 - 1 3 5 7 -j - 15 16 17 - 12 13 14 - 9 10 11 -k - back - 21 22 23 - 18 19 20 - front - 27 28 29 - 24 25 26 - - -Le noeud 1 est en (0.,0.,0.) et le côté de chaque cube est 1. -*/ - - -#include -#include -#include "std_e/utils/concatenate.hpp" - -namespace maia::six_hexa_mesh { - - const std::vector< std::array > cell_vtx = { - {1,2, 6, 5,13,14,18,17}, - {2,3, 7, 6,14,15,19,18}, - {3,4, 8, 7,15,16,20,19}, - {5,6,10, 9,17,18,22,21}, - {6,7,11,10,18,19,23,22}, - {7,8,12,11,19,20,24,23} - }; - - const std::vector< std::array > i_face_vtx = { - {1, 5,17,13}, - {5, 9,21,17}, - {2, 6,18,14}, - {6,10,22,18}, - {3, 7,19,15}, - {7,11,23,19}, - {4, 8,20,16}, - {8,12,24,20} - }; - - const std::vector< std::array > j_face_vtx = { - { 1,13,14, 2}, - { 2,14,15, 3}, - { 3,15,16, 4}, - { 5,17,18, 6}, - { 6,18,19, 7}, - { 7,19,20, 8}, - { 9,21,22,10}, - {10,22,23,11}, - {11,23,24,12} - }; - - const std::vector< std::array > k_face_vtx = { - { 1, 2, 6, 5}, - { 2, 3, 7, 6}, - { 3, 4, 8, 7}, - { 5, 6,10, 9}, - { 6, 7,11,10}, - { 7, 8,12,11}, - {13,14,18,17}, - {14,15,19,18}, - {15,16,20,19}, - {17,18,22,21}, - {18,19,23,22}, - {19,20,24,23} - }; - - const auto face_vtx = std_e::concatenate(i_face_vtx,j_face_vtx,k_face_vtx); - - const std::vector l_parents = { - 0,0, 1,4, 2,5, 3,6, // faces normal to i, by "face sheets" at i=1/2/3/4 - 0,0,0, 1,2,3, 4,5,6, // faces normal to j, by "face sheets" at j=1/2/3 - 0,0,0,0,0,0, 1,2,3,4,5,6 // faces normal to k, by "face sheets" at k=1/2 - }; - const std::vector r_parents = { - 1,4, 2,5, 3,6, 0,0, - 1,2,3, 4,5,6, 0,0,0, - 1,2,3,4,5,6, 0,0,0,0,0,0 - }; -} // maia::six_hexa_mesh - diff --git a/maia/__old/utils/cgns_tree_examples/test/unstructured_base.test.cpp b/maia/__old/utils/cgns_tree_examples/test/unstructured_base.test.cpp deleted file mode 100644 index ea70662a..00000000 --- a/maia/__old/utils/cgns_tree_examples/test/unstructured_base.test.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - - -#include "maia/__old/utils/cgns_tree_examples/unstructured_base.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "cpp_cgns/sids/Hierarchical_Structures.hpp" -#include "cpp_cgns/sids/Grid_Coordinates_Elements_and_Flow_Solution.hpp" -using namespace cgns; - - -TEST_CASE("unstructured mesh construction") { - auto base = create_unstructured_base(); - - // zone 0 - auto& z0 = get_child_by_label(base,"Zone_t"); - - // sizes - CHECK( VertexSize_U(z0) == 24 ); - CHECK( CellSize_U(z0) == 6 ); - CHECK( VertexBoundarySize_U(z0) == 0 ); - - // coordinates - tree& z0_coordX_node = get_node_by_matching(z0,"GridCoordinates/CoordinateX"); - auto z0_coordX = get_value(z0_coordX_node); - std::vector expected_z0_coord_X = { - 0.,1.,2.,3., - 0.,1.,2.,3., - 0.,1.,2.,3., - 0.,1.,2.,3., - 0.,1.,2.,3., - 0.,1.,2.,3. - }; - REQUIRE( z0_coordX.size() == expected_z0_coord_X.size() ); - for (size_t i=0; i(z0_inflow_pl_node); - REQUIRE( z0_inflow_pl.size() == 2 ); - CHECK( z0_inflow_pl[0] == 1 ); - CHECK( z0_inflow_pl[1] == 2 ); - - // gcs - tree& z0_grid_connec_pl_node = get_node_by_matching(z0,"ZoneGridConnectivity/MixingPlane/PointList"); - auto z0_grid_connec_pl = get_value(z0_grid_connec_pl_node); - tree& z0_grid_connec_pld_node = get_node_by_matching(z0,"ZoneGridConnectivity/MixingPlane/PointListDonor"); - auto z0_grid_connec_pld = get_value(z0_grid_connec_pld_node); - REQUIRE( z0_grid_connec_pl.size() == 1 ); - CHECK( z0_grid_connec_pl[0] == 7 ); - REQUIRE( z0_grid_connec_pld.size() == 1 ); - CHECK( z0_grid_connec_pld[0] == 1 ); - - // elements - tree& z0_ngon = get_child_by_name(z0,"Ngons"); - auto z0_ngon_elt_range = ElementRange(z0_ngon); - CHECK( z0_ngon_elt_range[0] == 1 ); - CHECK( z0_ngon_elt_range[1] == 8 + 9 + 12 ); - auto z0_ngon_elt_connect = ElementConnectivity(z0_ngon); - auto z0_ngon_eso = ElementStartOffset(z0_ngon); - REQUIRE( z0_ngon_eso.size() == 8+9+12 + 1 ); - REQUIRE( z0_ngon_elt_connect.size() == (8 + 9 + 12)*4 ); - CHECK( z0_ngon_elt_connect[0] == 1 ); - CHECK( z0_ngon_elt_connect[1] == 5 ); - CHECK( z0_ngon_elt_connect[2] == 17 ); - CHECK( z0_ngon_elt_connect[3] == 13 ); - auto z0_ngon_parent_elts = ParentElements(z0_ngon); - REQUIRE( z0_ngon_parent_elts.size() == (8 + 9 + 12)*2 ); - CHECK( z0_ngon_parent_elts(0,0) == 0 ); CHECK( z0_ngon_parent_elts(0,1) == 1 ); - CHECK( z0_ngon_parent_elts(1,0) == 0 ); CHECK( z0_ngon_parent_elts(1,1) == 4 ); - CHECK( z0_ngon_parent_elts(2,0) == 1 ); CHECK( z0_ngon_parent_elts(2,1) == 2 ); - CHECK( z0_ngon_parent_elts(3,0) == 4 ); CHECK( z0_ngon_parent_elts(3,1) == 5 ); -} -#endif // C++>17 diff --git a/maia/__old/utils/cgns_tree_examples/unstructured_base.cpp b/maia/__old/utils/cgns_tree_examples/unstructured_base.cpp deleted file mode 100644 index 8fb0acd5..00000000 --- a/maia/__old/utils/cgns_tree_examples/unstructured_base.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/utils/cgns_tree_examples/unstructured_base.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "maia/__old/generate/from_structured_grid.hpp" -#include "std_e/future/flatten.hpp" - -// TODO move { -#include "std_e/algorithm/iota.hpp" - -template auto -convert_to_ngons(const connectivity_range_type& cs) { - using connectivity_type = std::ranges::range_value_t; - constexpr int N = std::tuple_size_v; - std::vector eso(cs.size()+1); - std_e::exclusive_iota(begin(eso),end(eso),0,N); - return std::make_pair( - std::move(eso), - std_e::flat_view(cs) - ); -} -// END TODO } - - -using namespace cgns; -using namespace maia; - - -auto -create_GridCoords0() -> tree { - auto coord_X = node_value( - { 0.,1.,2.,3., - 0.,1.,2.,3., - 0.,1.,2.,3., - 0.,1.,2.,3., - 0.,1.,2.,3., - 0.,1.,2.,3. } - ); - auto coord_Y = node_value( - { 0.,0.,0.,0., - 1.,1.,1.,1., - 2.,2.,2.,2., - 0.,0.,0.,0., - 1.,1.,1.,1., - 2.,2.,2.,2. } - ); - auto coord_Z = node_value( - { 0.,0.,0.,0., - 0.,0.,0.,0., - 0.,0.,0.,0., - 1.,1.,1.,1., - 1.,1.,1.,1., - 1.,1.,1.,1. } - ); - tree grid_coords = new_GridCoordinates(); - emplace_child(grid_coords,new_DataArray("CoordinateX",std::move(coord_X))); - emplace_child(grid_coords,new_DataArray("CoordinateY",std::move(coord_Y))); - emplace_child(grid_coords,new_DataArray("CoordinateZ",std::move(coord_Z))); - return grid_coords; -} -auto -create_GridCoords1() -> tree { - auto coord_X = node_value( - { 3.,4., - 3.,4., - 3.,4., - 3.,4. } - ); - auto coord_Y = node_value( - { 0.,0., - 1.,1., - 0.,0., - 1.,1. } - ); - auto coord_Z = node_value( - { 0.,0., - 0.,0., - 1.,1., - 1.,1. } - ); - tree grid_coords = new_GridCoordinates(); - emplace_child(grid_coords,new_DataArray("CoordinateX",std::move(coord_X))); - emplace_child(grid_coords,new_DataArray("CoordinateY",std::move(coord_Y))); - emplace_child(grid_coords,new_DataArray("CoordinateZ",std::move(coord_Z))); - return grid_coords; -} - - - - -auto -create_Zone0() -> tree { -/* Mesh used: "six quads", cf. simple_meshes.txt */ - int32_t VertexSize = 24; - int32_t CellSize = 6; - int32_t VertexSizeBoundary = 0; - tree zone = new_UnstructuredZone("Zone0",{VertexSize,CellSize,VertexSizeBoundary}); - - emplace_child(zone,create_GridCoords0()); - - tree zone_bc = new_ZoneBC(); - emplace_child(zone_bc,new_BC("Inlet","FaceCenter",{1,2})); // 1,2 are the two i-faces at x=0 - emplace_child(zone,std::move(zone_bc)); - - tree gc = new_GridConnectivity("MixingPlane","Zone1","FaceCenter","Abutting1to1"); - emplace_child(gc,new_PointList("PointList",{7})); // 7 is the bottom i-face at x=3 - emplace_child(gc,new_PointList("PointListDonor",{1})); // cf. zone 1 - tree zone_gc = new_ZoneGridConnectivity(); - emplace_child(zone_gc,std::move(gc)); - emplace_child(zone,std::move(zone_gc)); - - - std_e::multi_index vertex_dims = {4,3,2}; - auto quad_faces = generate_faces(vertex_dims) | std_e::to_vector(); - auto [eso,ngons_r] = convert_to_ngons(quad_faces); - auto ngons = ngons_r | std_e::to_vector(); - - I8 nb_i_faces = 8; - I8 nb_j_faces = 9; - I8 nb_k_faces = 12; - int32_t nb_ngons = nb_i_faces + nb_j_faces + nb_k_faces; - - auto i_faces_l_parent_elements = generate_l_parents(vertex_dims,0); - auto i_faces_r_parent_elements = generate_r_parents(vertex_dims,0); - auto j_faces_l_parent_elements = generate_l_parents(vertex_dims,1); - auto j_faces_r_parent_elements = generate_r_parents(vertex_dims,1); - // k-faces are considered interior (only for the sake of having interior nodes), - // their parent is imaginary cell #42 - auto k_faces_l_parent_elements = std_e::ranges::repeat(42,nb_k_faces); - auto k_faces_r_parent_elements = std_e::ranges::repeat(42,nb_k_faces); - - auto parent_elements = std_e::views::concat( - i_faces_l_parent_elements , j_faces_l_parent_elements, k_faces_l_parent_elements, - i_faces_r_parent_elements , j_faces_r_parent_elements, k_faces_r_parent_elements - ) | std_e::to_vector(); - - std_e::multi_index pe_dims = {(int)parent_elements.size()/2,2}; - md_array parent_elts(std::move(parent_elements),pe_dims); - - tree ngon_elts = new_NgonElements( - "Ngons", - std::move(ngons), - 1,nb_ngons - ); - emplace_child(ngon_elts,new_DataArray("ParentElements", node_value(std::move(parent_elts)))); - emplace_child(ngon_elts,new_DataArray("ElementStartOffset", node_value(std::move(eso)))); - emplace_child(zone,std::move(ngon_elts)); - - std::vector nface = { -// i i j j k k - 1,3, 9,12, 18,24, - 3,5, 10,13, 19,25, - 5,7, 11,14, 20,26, - 2,4, 12,15, 21,27, - 4,6, 13,16, 22,28, - 6,8, 14,17, 23,29, - }; - std::vector nface_eso = {0,6,12,18,24,30,36}; - tree nface_elts = new_NfaceElements( - "Nfaces", - std::move(nface), - nb_ngons+1,nb_ngons+6 - ); - emplace_child(nface_elts,new_DataArray("ElementStartOffset", node_value(std::move(nface_eso)))); - emplace_child(zone,std::move(nface_elts)); - return zone; -} - -auto -create_Zone1() -> tree { -/* Le maillage utilisé est "one quad", cf. simple_meshes.h */ - int32_t VertexSize = 8; - int32_t CellSize = 1; - int32_t VertexSizeBoundary = 0; - tree zone = new_UnstructuredZone("Zone1",{VertexSize,CellSize,VertexSizeBoundary}); - - emplace_child(zone,create_GridCoords1()); - - tree zone_bc = new_ZoneBC(); - emplace_child(zone_bc,new_BC("Outlet","FaceCenter",{2})); // 2 is the i-face at x=4 - emplace_child(zone,std::move(zone_bc)); - - - tree gc = new_GridConnectivity("MixingPlane","Zone0","FaceCenter","Abutting1to1"); - emplace_child(gc,new_PointList("PointList",{1})); // cf. zone 0 - emplace_child(gc,new_PointList("PointListDonor",{7})); // 1 is the i-face at x=3 - tree zone_gc = new_ZoneGridConnectivity(); - emplace_child(zone_gc,std::move(gc)); - emplace_child(zone,std::move(zone_gc)); - - - std_e::multi_index vertex_dims = {2,2,2}; - auto quad_faces = generate_faces(vertex_dims) | std_e::to_vector(); - auto [eso,ngons_r] = convert_to_ngons(quad_faces); - auto ngons = ngons_r | std_e::to_vector(); - - - int32_t nb_ngons = 2 + 2 + 2; - - auto parent_elements = generate_faces_parent_cell_ids(vertex_dims) | std_e::to_vector(); - - std_e::multi_index pe_dims = {(I8)parent_elements.size()/2,2}; - md_array parent_elts(std::move(parent_elements),std_e::dyn_shape(pe_dims)); - - tree ngon_elts = new_NgonElements( - "Ngons", - std::move(ngons), - 1,nb_ngons - ); - emplace_child(ngon_elts,new_DataArray("ParentElements", node_value(std::move(parent_elts)))); - emplace_child(ngon_elts,new_DataArray("ElementStartOffset", node_value(std::move(eso)))); - emplace_child(zone,std::move(ngon_elts)); - - std::vector nface = { -// i i j j k k - 1,2, 3,4, 5,6, - }; - std::vector nface_eso = {0,6}; - tree nface_elts = new_NfaceElements( - "Nfaces", - std::move(nface), - nb_ngons+1,nb_ngons+1 - ); - emplace_child(nface_elts,new_DataArray("ElementStartOffset", node_value(std::move(nface_eso)))); - emplace_child(zone,std::move(nface_elts)); - return zone; -} - -auto -create_unstructured_base() -> cgns::tree { - tree b = new_CGNSBase("Base0",3,3); - emplace_child(b,create_Zone0()); - emplace_child(b,create_Zone1()); - return b; -} - -#endif // C++>17 diff --git a/maia/__old/utils/cgns_tree_examples/unstructured_base.hpp b/maia/__old/utils/cgns_tree_examples/unstructured_base.hpp deleted file mode 100644 index 623da45b..00000000 --- a/maia/__old/utils/cgns_tree_examples/unstructured_base.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" - - -auto create_unstructured_base() -> cgns::tree; diff --git a/maia/__old/utils/neighbor_graph.cpp b/maia/__old/utils/neighbor_graph.cpp deleted file mode 100644 index 9c5cd37b..00000000 --- a/maia/__old/utils/neighbor_graph.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/__old/utils/neighbor_graph.hpp" -#include "cpp_cgns/cgns.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "std_e/utils/vector.hpp" -#include "std_e/parallel/mpi.hpp" -#include "std_e/utils/to_string.hpp" -#include "maia/utils/parallel/exchange/spread_then_collect.hpp" - - -namespace cgns { - -auto -name_of_zones(const tree& b) -> std::vector { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - auto zones = get_children_by_label(b,"Zone_t"); - - std::vector z_names; - for (const tree& z : zones) { - z_names.push_back(name(z)); - } - - return z_names; -} - -auto -name_of_mentionned_zones(const tree& b) -> std::vector { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - auto zones = get_children_by_label(b,"Zone_t"); - - std::vector z_names; - for (const tree& z : zones) { - z_names.push_back(name(z)); - if (has_child_of_name(z,"ZoneGridConnectivity")) { - const tree& zgc = get_child_by_name(z,"ZoneGridConnectivity"); - auto gcs = get_children_by_label(zgc,"GridConnectivity_t"); - for (const tree& gc : gcs) { - std::string opp_zone_name = to_string(value(gc)); - z_names.push_back(opp_zone_name); - } - } - } - - std_e::sort_unique(z_names); - return z_names; -} - -auto -create_connectivity_infos(tree& b) -> std::vector { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - auto zones = get_children_by_label(b,"Zone_t"); - - std::vector cis; - for (tree& z : zones) { - if (has_child_of_name(z,"ZoneGridConnectivity")) { - tree& zgc = get_child_by_name(z,"ZoneGridConnectivity"); - auto gcs = get_children_by_label(zgc,"GridConnectivity_t"); - for (tree& gc : gcs) { - std::string opp_zone_name = to_string(value(gc)); - cis.push_back({name(z),opp_zone_name,&gc}); - } - } - } - - return cis; -} - -auto -paths_of_all_mentionned_zones(const tree& b) -> cgns_paths { - auto all_z_names = std_e::concatenate(name_of_zones(b),name_of_mentionned_zones(b)); - std_e::sort_unique(all_z_names); - - cgns_paths z_paths; - for (const auto& z_name : all_z_names) { - z_paths.push_back("/"+name(b)+"/"+z_name); - } - return z_paths; -} - -auto -compute_zone_infos(const tree& b, MPI_Comm comm) -> zone_infos { - auto paths = paths_of_all_mentionned_zones(b); - label_registry zone_reg(paths,comm); - - auto owned_zone_names = name_of_zones(b); - int nb_owned_zones = owned_zone_names.size(); - std::vector owned_zone_ids(nb_owned_zones); - for (int i=0; i neighbor_zone_ids_long(nb_neighbor_zones); - for (int i=0; i proc_of_owned_zones(nb_owned_zones,std_e::rank(comm)); - - std_e::interval_vector zone_reg_long(begin(zone_reg.distribution()), end(zone_reg.distribution())); - auto proc_of_neighbor_zones_long = spread_then_collect( - comm, zone_reg_long, - owned_zone_ids, proc_of_owned_zones, - neighbor_zone_ids_long - ); - - std::vector proc_of_neighbor_zones(begin(proc_of_neighbor_zones_long), end(proc_of_neighbor_zones_long)); - std::vector neighbor_zone_ids(begin(neighbor_zone_ids_long), end(neighbor_zone_ids_long)); - - - return {std::move(neighbor_zone_names),std::move(neighbor_zone_ids),std::move(proc_of_neighbor_zones)}; -} - - -auto -donor_zones_ranks(const zone_infos& zis, const std::vector& cis) -> std::vector { - std::vector ranks = std_e::transform(cis,[&zis](const auto& ci){ return find_donor_proc(ci,zis); }); - std_e::sort_unique(ranks); - return ranks; -} - - -} // cgns -#endif // C++>17 diff --git a/maia/__old/utils/neighbor_graph.hpp b/maia/__old/utils/neighbor_graph.hpp deleted file mode 100644 index 3b72d2da..00000000 --- a/maia/__old/utils/neighbor_graph.hpp +++ /dev/null @@ -1,188 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "mpi.h" -#include "maia/algo/part/cgns_registry/cgns_registry.hpp" -#include "std_e/data_structure/jagged_range.hpp" - -#include "std_e/parallel/all_to_all.hpp" -#include "std_e/parallel/dist_graph.hpp" -#include "std_e/algorithm/partition_sort.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "cpp_cgns/sids/Building_Block_Structure_Definitions.hpp" - - -namespace cgns { - - -struct zone_infos { - std::vector names; - std::vector ids; - std::vector procs; -}; -inline auto find_id_from_name(const zone_infos& zis, const std::string& z_name) -> int { - auto it = find_if(begin(zis.names),end(zis.names),[&](const auto& n){ return n == z_name; }); - STD_E_ASSERT(it!=end(zis.names)); - auto idx = it-begin(zis.names); - return zis.ids[idx]; -} -inline auto find_name_from_id(const zone_infos& zis, int z_id) -> const std::string& { - auto it = find_if(begin(zis.ids),end(zis.ids),[=](const auto& id){ return id == z_id; }); - STD_E_ASSERT(it!=end(zis.ids)); - auto idx = it-begin(zis.ids); - return zis.names[idx]; -} - -struct connectivity_info { - std::string zone_name; - std::string zone_donor_name; - tree* node; -}; -inline auto -find_donor_proc(const connectivity_info& x, const zone_infos& zis) -> int { - auto it = std::find(begin(zis.names),end(zis.names),x.zone_donor_name); - int idx = it-begin(zis.names); - return zis.procs[idx]; -} - - -auto -paths_of_all_mentionned_zones(const tree& b) -> cgns_paths; - -auto -compute_zone_infos(const tree& b, MPI_Comm comm) -> zone_infos; -auto -create_connectivity_infos(tree& b) -> std::vector; - -auto -donor_zones_ranks(const zone_infos& zis, const std::vector& cis) -> std::vector; - -// TODO replace by std_e::multi_range -struct pl_by_donor_zone { - std::vector donor_z_names; - std::vector receiver_z_names; - std::vector locs; - std_e::jagged_vector plds; -}; -// Note: for now, only PointList and PointListDonor can be exchanged -class zone_exchange { - private: - zone_infos zis; - MPI_Comm receiver_to_donor_comm; - MPI_Comm donor_to_receiver_comm; - std_e::jagged_vector cis; - std_e::interval_vector proc_indices_in_donor; - public: - zone_exchange() = default; - zone_exchange(tree& b, MPI_Comm comm) - { - zis = cgns::compute_zone_infos(b,comm); - auto cis_ = cgns::create_connectivity_infos(b); - - std::vector donor_ranks = donor_zones_ranks(zis,cis_); - - receiver_to_donor_comm = std_e::dist_graph_create_adj(comm,donor_ranks); - - cis = std_e::sort_into_partitions(std::move(cis_),[&](const auto& ci){ return find_donor_proc(ci,zis); }); - - proc_indices_in_donor = neighbor_all_to_all(cis.indices(),receiver_to_donor_comm); - - // receiver_ranks { - int nb_donor_procs = donor_ranks.size(); - std::vector procs(nb_donor_procs,std_e::rank(receiver_to_donor_comm)); - std::vector receiver_ranks = std_e::neighbor_all_to_all(procs,receiver_to_donor_comm); - - donor_to_receiver_comm = std_e::dist_graph_create_adj(comm,receiver_ranks); - // receiver_ranks } - } - - auto - send_pl_to_donor_proc(const std::string& pl_name) -> pl_by_donor_zone { - STD_E_ASSERT(pl_name=="PointList" || pl_name=="PointListDonor"); - std::vector pld_cat; - std::vector neighbor_data_indices; - std::vector donor_z_ids; - std::vector locs_neigh; - int cur = 0; - for (const auto& ci : cis.flat_view()) { - tree& gc = *ci.node; - tree& pld = get_child_by_name(gc,pl_name); - auto pld_span = get_value(pld); - std_e::append(pld_cat,pld_span); - - neighbor_data_indices.push_back(cur); - cur += pld_span.size(); - - const std::string& donor_z_name = ci.zone_donor_name; - int donor_z_id = find_id_from_name(zis,donor_z_name); - donor_z_ids.push_back(donor_z_id); - - auto loc_str = to_string(value(get_child_by_name(gc,"GridLocation"))); - auto loc = std_e::to_enum(loc_str); - locs_neigh.push_back(loc); - } - neighbor_data_indices.push_back(cur); - - std::vector> z_names; - for (int i=0; i z_by_proc; - for (const auto& ci : cis_by_proc) { - const std::string& z_name = ci.zone_name; - z_by_proc.push_back(z_name); - } - z_names.push_back(z_by_proc); - } - - std_e::jagged_vector pl_data_from(std::move(pld_cat),std::move(neighbor_data_indices),cis.indices()); - auto pl_data_3 = neighbor_all_to_all_v(pl_data_from,receiver_to_donor_comm); - auto pl_data = std_e::flatten_last_level(std::move(pl_data_3)); - - auto target_z_names = std_e::neighbor_all_to_all_v(z_names,receiver_to_donor_comm); - auto [target_donor_z_ids,_1] = neighbor_all_to_all_v_from_indices(donor_z_ids,cis.indices(),receiver_to_donor_comm); - auto donor_z_names = std_e::transform(target_donor_z_ids,[this](int z_id){ return find_name_from_id(this->zis,z_id); }); - - auto [target_locs,_2] = neighbor_all_to_all_v_from_indices(locs_neigh,cis.indices(),receiver_to_donor_comm); - - // flatten - std::vector target_z_names2; - for (const auto& z_names_by_receiver_rank : target_z_names) { - for (const auto& z_name : z_names_by_receiver_rank) { - target_z_names2.push_back(z_name); - } - } - - return {std::move(donor_z_names) , std::move(target_z_names2) , std::move(target_locs) , std::move(pl_data)}; - } - - auto - send_PointList_to_donor_proc() -> pl_by_donor_zone { - return send_pl_to_donor_proc("PointList"); - } - auto - send_PointListDonor_to_donor_proc() -> pl_by_donor_zone { - return send_pl_to_donor_proc("PointListDonor"); - } - - auto - receive_PointListDonor_from_donor_proc(const std_e::jagged_vector& pl_donor_data) -> void { - auto pl_donor_data_by_proc = std_e::view_with_new_level(pl_donor_data,proc_indices_in_donor); - auto pl_neighbor_data = neighbor_all_to_all_v(pl_donor_data_by_proc,donor_to_receiver_comm); - auto pl_neighbor_data_cat = std_e::flattened_last_level_view(pl_neighbor_data); - auto nb_joins = pl_neighbor_data_cat.size(); - STD_E_ASSERT(nb_joins==(int)cis.flat_view().size()); - for (int i=0; i(pld); - auto new_pld_span = pl_neighbor_data_cat[i]; - STD_E_ASSERT(size_t(new_pld_span.size())==size_t(pld_span.size())); - std::copy(begin(new_pld_span),end(new_pld_span),begin(pld_span)); - } - } -}; - - -} // cgns diff --git a/maia/__old/utils/test/neighbor_graph.test.cpp b/maia/__old/utils/test/neighbor_graph.test.cpp deleted file mode 100644 index f1f4be6b..00000000 --- a/maia/__old/utils/test/neighbor_graph.test.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" -#include "doctest/extensions/doctest_mpi.h" - -#include "maia/__old/utils/cgns_tree_examples/base_two_ranks.hpp" -#include "maia/__old/utils/neighbor_graph.hpp" - -using namespace cgns; -using namespace std; -using namespace std_e; - - -MPI_TEST_CASE("paths_of_all_mentionned_zones",2) { - cgns::tree b = example::create_base_two_ranks(test_rank); - - auto paths = paths_of_all_mentionned_zones(b); - - vector expected_paths_0 = { - "/Base/Zone0", - "/Base/Zone1", - "/Base/Zone3" - }; - vector expected_paths_1 = { - "/Base/Zone0", - "/Base/Zone1", - "/Base/Zone2", - "/Base/Zone3" - }; - - MPI_CHECK(0, paths == expected_paths_0); - MPI_CHECK(1, paths == expected_paths_1); -} - -MPI_TEST_CASE("zone_infos",2) { - cgns::tree b = example::create_base_two_ranks(test_rank); - - zone_infos zis = cgns::compute_zone_infos(b,test_comm); - - MPI_CHECK(0, zis.names == vector{"Zone0","Zone1","Zone3"} ); - MPI_CHECK(0, zis.procs == vector< int >{ 0 , 1 , 0 } ); - MPI_CHECK(1, zis.names == vector{"Zone0","Zone1","Zone2","Zone3"} ); - MPI_CHECK(1, zis.procs == vector< int >{ 0 , 1 , 1 , 0 } ); -} -MPI_TEST_CASE("connectivity_infos",2) { - cgns::tree b = example::create_base_two_ranks(test_rank); - - std::vector cis = cgns::create_connectivity_infos(b); - - MPI_CHECK(0, cis.size() == 4 ); - MPI_CHECK(0, cis[0].zone_name == "Zone0" ); MPI_CHECK(0, cis[0].zone_donor_name == "Zone0" ); - MPI_CHECK(0, cis[1].zone_name == "Zone0" ); MPI_CHECK(0, cis[1].zone_donor_name == "Zone1" ); - MPI_CHECK(0, cis[2].zone_name == "Zone3" ); MPI_CHECK(0, cis[2].zone_donor_name == "Zone1" ); - MPI_CHECK(0, cis[3].zone_name == "Zone3" ); MPI_CHECK(0, cis[3].zone_donor_name == "Zone1" ); - MPI_CHECK(1, cis.size() == 3 ); - MPI_CHECK(1, cis[0].zone_name == "Zone1" ); MPI_CHECK(1, cis[0].zone_donor_name == "Zone1" ); - MPI_CHECK(1, cis[1].zone_name == "Zone1" ); MPI_CHECK(1, cis[1].zone_donor_name == "Zone0" ); - MPI_CHECK(1, cis[2].zone_name == "Zone2" ); MPI_CHECK(1, cis[2].zone_donor_name == "Zone3" ); -} - - -MPI_TEST_CASE("send_PointListDonor_to_donor_proc",2) { - auto b = example::create_base_two_ranks(test_rank); - - zone_exchange ze(b,test_comm); - auto [donor_zone_names,receiver_zone_names,grid_locs,pl_donor_data] = ze.send_PointListDonor_to_donor_proc(); - - MPI_CHECK( 0 , donor_zone_names == vector{"Zone0","Zone0","Zone3"} ); - MPI_CHECK( 0 , receiver_zone_names == vector{"Zone0","Zone1","Zone2"} ); - MPI_CHECK( 0 , grid_locs == vector{FaceCenter,Vertex,CellCenter} ); - MPI_CHECK( 0 , pl_donor_data == jagged_vector{{1,2,3},{111,112},{136,137}} ); - - MPI_CHECK( 1 , donor_zone_names == vector{"Zone1","Zone1","Zone1","Zone1"} ); - MPI_CHECK( 1 , receiver_zone_names == vector{"Zone0","Zone3","Zone3","Zone1"} ); - MPI_CHECK( 1 , grid_locs == vector{FaceCenter,Vertex,Vertex,CellCenter} ); - MPI_CHECK( 1 , pl_donor_data == jagged_vector{{11,12,13,14},{15},{16,17},{101,102,103,104}} ); -} - -MPI_TEST_CASE("receive_PointListDonor_from_donor_proc",2) { - auto b = example::create_base_two_ranks(test_rank); - - zone_exchange ze(b,test_comm); - - jagged_vector pl_donor_data; - if (test_rank==0) { - // "Zone0" , "Zone0" , "Zone3" - pl_donor_data = {{100,200,300},{11100,11200},{13600,13700}}; - } - if (test_rank==1) { - // "Zone1" ,"Zone1", "Zone1" , "Zone1" - pl_donor_data = {{1100,1200,1300,1400},{1500},{1600,1700},{10100,10200,10300,10400}}; - } - - ze.receive_PointListDonor_from_donor_proc(pl_donor_data); - - if (test_rank==0) { - auto pl_join0 = get_node_value_by_matching(b,"Zone0/ZoneGridConnectivity/Join0/PointListDonor"); - auto pl_join1 = get_node_value_by_matching(b,"Zone0/ZoneGridConnectivity/Join1/PointListDonor"); - auto pl_join2 = get_node_value_by_matching(b,"Zone3/ZoneGridConnectivity/Join2/PointListDonor"); - auto pl_join3 = get_node_value_by_matching(b,"Zone3/ZoneGridConnectivity/Join3/PointListDonor"); - MPI_CHECK( 0 , pl_join0 == vector{100,200,300} ); - MPI_CHECK( 0 , pl_join1 == vector{1100,1200,1300,1400} ); - MPI_CHECK( 0 , pl_join2 == vector{1500} ); - MPI_CHECK( 0 , pl_join3 == vector{1600,1700} ); - } - if (test_rank==1) { - auto pl_join4 = get_node_value_by_matching(b,"Zone1/ZoneGridConnectivity/Join4/PointListDonor"); - auto pl_join5 = get_node_value_by_matching(b,"Zone1/ZoneGridConnectivity/Join5/PointListDonor"); - auto pl_join6 = get_node_value_by_matching(b,"Zone2/ZoneGridConnectivity/Join6/PointListDonor"); - MPI_CHECK( 1 , pl_join4 == vector{10100,10200,10300,10400} ); - MPI_CHECK( 1 , pl_join5 == vector{11100,11200} ); - MPI_CHECK( 1 , pl_join6 == vector{13600,13700} ); - } -} -#endif // C++>17 diff --git a/maia/algo/__init__.py b/maia/algo/__init__.py deleted file mode 100644 index 45834756..00000000 --- a/maia/algo/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from . import dist, part, seq - -from .indexing import pe_to_nface, nface_to_pe - -from .transform import transform_affine, \ - scale_mesh - diff --git a/maia/algo/apply_function_to_nodes.py b/maia/algo/apply_function_to_nodes.py deleted file mode 100644 index 291206db..00000000 --- a/maia/algo/apply_function_to_nodes.py +++ /dev/null @@ -1,36 +0,0 @@ -import maia.pytree as PT - -def apply_to_bases(f, t, *args): - if PT.get_label(t)=="CGNSBase_t": - b_iter = [t] - elif PT.get_label(t)=="CGNSTree_t": - b_iter = PT.iter_children_from_label(t, "CGNSBase_t") - else: - raise Exception("function \""+f.__name__+"\"" \ - " can only be applied to a \"CGNSBase_t\" or on a complete \"CGNSTree_t\"," \ - " not on a node of type \""+PT.get_label(t)+"\".") - for b in b_iter: - f(b, *args) - -def zones_iterator(t): - """ - Helper iterator to loop over zones from a tree, a base or a zone node - """ - if PT.get_label(t)=="Zone_t": - yield t - elif PT.get_label(t)=="CGNSBase_t": - yield from PT.iter_children_from_label(t, "Zone_t") - elif PT.get_label(t)=="CGNSTree_t": - yield from PT.iter_children_from_labels(t, ["CGNSBase_t", "Zone_t"]) - else: - raise ValueError("Unvalid object for zones iterator") - -def apply_to_zones(f, t, *args): - if PT.get_label(t) not in ["Zone_t", "CGNSBase_t", "CGNSTree_t"]: - raise Exception("function \""+f.__name__+"\"" \ - " can only be applied to a \"Zone_t\", a \"CGNSBase_t\" or on a complete \"CGNSTree_t\"," \ - " not on a node of type \""+PT.get_label(t)+"\".") - else: - for z in zones_iterator(t): - f(z, *args) - diff --git a/maia/algo/common/poly_algorithm.cpp b/maia/algo/common/poly_algorithm.cpp deleted file mode 100644 index a1ba4e32..00000000 --- a/maia/algo/common/poly_algorithm.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/common/poly_algorithm.hpp" -#include "cpp_cgns/cgns.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include -#include -#include "cpp_cgns/sids/utils.hpp" -#include "maia/pytree/maia/element_sections.hpp" -#include "maia/algo/common/shift.hpp" -#include "std_e/data_structure/block_range/vblock_range.hpp" -#include "std_e/data_structure/block_range/ivblock_range.hpp" - -using namespace cgns; - -namespace maia { - -template auto -_indexed_to_interleaved_connectivity(tree& elt) -> void { - auto eso = ElementStartOffset(elt); - auto new_connectivity = ElementConnectivity(elt); - auto new_poly_range = std_e::view_as_vblock_range(new_connectivity,eso); - - I old_connectivity_sz = eso.size()-1 + eso.back(); - std::vector old_connectivity(old_connectivity_sz); - auto old_poly_range = std_e::view_as_ivblock_range(old_connectivity); - - std::ranges::copy(new_poly_range,old_poly_range.begin()); - - rm_child_by_name(elt,"ElementConnectivity"); - rm_child_by_name(elt,"ElementStartOffset"); - - emplace_child(elt,new_DataArray("ElementConnectivity",std::move(old_connectivity))); -} - -auto -indexed_to_interleaved_connectivity(tree& elt) -> void { - STD_E_ASSERT(label(elt)=="Elements_t"); - if (value(elt).data_type()=="I4") return _indexed_to_interleaved_connectivity(elt); - if (value(elt).data_type()=="I8") return _indexed_to_interleaved_connectivity(elt); -} - - -template auto -_interleaved_to_indexed_connectivity(tree& elt) -> void { - auto old_connectivity = ElementConnectivity(elt); - auto old_poly_range = std_e::view_as_ivblock_range(old_connectivity); - - auto elt_range = element_range(elt); - auto n_elt = length(elt_range); - std::vector eso(n_elt+1); - std::vector new_connectivity(old_connectivity.size()-n_elt); - auto new_poly_range = std_e::view_as_vblock_range(new_connectivity,eso); - - std::ranges::copy(old_poly_range,new_poly_range.begin()); - - rm_child_by_name(elt,"ElementConnectivity"); - - emplace_child(elt,new_DataArray("ElementStartOffset",std::move(eso))); - emplace_child(elt,new_DataArray("ElementConnectivity",std::move(new_connectivity))); -} - -auto -interleaved_to_indexed_connectivity(tree& elt) -> void { - STD_E_ASSERT(label(elt)=="Elements_t"); - if (value(elt).data_type()=="I4") return _interleaved_to_indexed_connectivity(elt); - if (value(elt).data_type()=="I8") return _interleaved_to_indexed_connectivity(elt); -} - -} // maia -#endif // C++>17 diff --git a/maia/algo/common/poly_algorithm.hpp b/maia/algo/common/poly_algorithm.hpp deleted file mode 100644 index 593d4120..00000000 --- a/maia/algo/common/poly_algorithm.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" - - -namespace maia { - -auto indexed_to_interleaved_connectivity(cgns::tree& elt) -> void; -auto interleaved_to_indexed_connectivity(cgns::tree& elt) -> void; - -} // maia diff --git a/maia/algo/common/shift.hpp b/maia/algo/common/shift.hpp deleted file mode 100644 index f2048934..00000000 --- a/maia/algo/common/shift.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - - -#include "cpp_cgns/sids.hpp" - - -namespace maia { - - -template auto -shift_element_ranges(Tree_range& elt_sections, I offset) -> void { - for (cgns::tree& elt_section : elt_sections) { - auto elt_interval = ElementRange(elt_section); - elt_interval[0] += offset; - elt_interval[1] += offset; - } -} -template auto -shift_parent_elements(Tree_range& face_sections, I offset) -> void { - for (cgns::tree& face_section : face_sections) { - if (has_child_of_name(face_section,"ParentElements")) { - auto pe = get_node_value_by_matching(face_section,"ParentElements"); - for (I& id : pe) { - if (id != 0) { - id += offset; - } - } - } - } -} -template auto -shift_cell_ids(cgns::tree& z, I offset) -> void { - auto cell_sections = volume_element_sections(z); - shift_element_ranges(cell_sections,offset); - - auto face_sections = surface_element_sections(z); - shift_parent_elements(face_sections,offset); - - // TODO also shift all PointList located at CellCenter -} - - -} // maia diff --git a/maia/algo/common/test/poly_algorithm.test.cpp b/maia/algo/common/test/poly_algorithm.test.cpp deleted file mode 100644 index aacb0d58..00000000 --- a/maia/algo/common/test/poly_algorithm.test.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest_pybind.hpp" - -#include "maia/io/file.hpp" -#include "maia/utils/mesh_dir.hpp" -#include "cpp_cgns/tree_manip.hpp" - -#include "maia/algo/dist/elements_to_ngons/elements_to_ngons.hpp" -#include "maia/algo/common/poly_algorithm.hpp" - - -using cgns::I4; -using cgns::I8; -using cgns::tree; - - -PYBIND_TEST_CASE("indexed_to_interleaved_connectivity") { - // setup - std::string file_name = maia::mesh_dir+"hex_2_prism_2.yaml"; - tree t = maia::file_to_dist_tree(file_name,MPI_COMM_SELF); - tree& z = cgns::get_node_by_matching(t,"Base/Zone"); - maia::elements_to_ngons(z,MPI_COMM_SELF); // Note: we are not testing that, its just a way to get an ngon test - tree& ngons = cgns::get_child_by_name(z,"NGON_n"); - - // apply tested function - maia::indexed_to_interleaved_connectivity(ngons); - - // check - auto connec = cgns::get_node_value_by_matching(z,"NGON_n/ElementConnectivity"); - CHECK(connec == std::vector{ - 4, 1,6, 9,4, 4, 6 ,11,14, 9, 4, 3, 5,10, 8, 4, 8,10,15,13, - 4, 1,2, 7,6, 4, 2 , 3, 8, 7, 4, 6, 7,12,11, 4, 7, 8,13,12, - 4, 4,9,10,5, 4, 9 ,14,15,10, 4, 1, 4, 5, 2, 4, 11,12,15,14, - 3, 2,5,3 , 3, 12,13,15 , 3, 7, 8,10, - 4, 2,5,10,7, 4, 6 , 7,10, 9, 4, 7,10,15,12} ); - - CHECK( !cgns::has_node(z,"NGON_n/ElementStartOffset") ); -} - -PYBIND_TEST_CASE("interleaved_to_indexed_connectivity") { - // setup - std::string file_name = maia::mesh_dir+"hex_2_prism_2.yaml"; - tree t = maia::file_to_dist_tree(file_name,MPI_COMM_SELF); - tree& z = cgns::get_node_by_matching(t,"Base/Zone"); - maia::elements_to_ngons(z,MPI_COMM_SELF); // We are not testing that, its just a way to get an ngon test - tree& ngons = cgns::get_child_by_name(z,"NGON_n"); - maia::indexed_to_interleaved_connectivity(ngons); // Not testing that either - - // apply tested function - maia::interleaved_to_indexed_connectivity(ngons); - - // check - auto eso = cgns::get_node_value_by_matching(z,"NGON_n/ElementStartOffset"); - CHECK( eso == std::vector{0,4,8,12,16,20,24,28,32,36,40,44,48,51,54,57,61,65,69} ); - - auto connec = cgns::get_node_value_by_matching(z,"NGON_n/ElementConnectivity"); - CHECK( connec == std::vector{1,6, 9,4, 6,11,14, 9, 3, 5,10, 8, 8,10,15,13, - 1,2, 7,6, 2, 3, 8, 7, 6, 7,12,11, 7, 8,13,12, - 4,9,10,5, 9,14,15,10, 1, 4, 5, 2, 11,12,15,14, - 2,5, 3, 12,13,15 , 7, 8,10, - 2,5,10,7, 6, 7,10, 9, 7,10,15,12} ); -} -#endif // C++>17 diff --git a/maia/algo/dist/__init__.py b/maia/algo/dist/__init__.py deleted file mode 100644 index 6b0c0e99..00000000 --- a/maia/algo/dist/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Distributed algorithms for distributed trees -""" - -from .conformize_jn import conformize_jn_pair - -from .connect_match import connect_1to1_families - -from .duplicate import duplicate_from_periodic_jns,\ - duplicate_from_rotation_jns_to_360 - -from .elements_to_ngons import elements_to_ngons -elements_to_poly = elements_to_ngons - -from .extract_surf_dmesh import extract_surf_tree_from_bc - -from .merge import merge_all_zones_from_families,\ - merge_connected_zones,\ - merge_zones,\ - merge_zones_from_family - -from .mesh_adaptation import adapt_mesh_with_feflo - -from .mixed_to_std_elements import convert_mixed_to_elements - -from .ngon_from_std_elements import convert_elements_to_ngon,\ - generate_ngon_from_std_elements - -from .ngons_to_elements import ngons_to_elements -poly_to_elements = ngons_to_elements - -from .rearrange_element_sections import rearrange_element_sections - -from .redistribute import redistribute_tree - -from .s_to_u import convert_s_to_u, convert_s_to_ngon - -from .std_elements_to_mixed import convert_elements_to_mixed - -from .vertex_list import generate_jn_vertex_list,\ - generate_jns_vertex_list diff --git a/maia/algo/dist/adaptation_utils.py b/maia/algo/dist/adaptation_utils.py deleted file mode 100644 index fd4cfe1a..00000000 --- a/maia/algo/dist/adaptation_utils.py +++ /dev/null @@ -1,977 +0,0 @@ -import mpi4py.MPI as MPI - -import maia -import maia.pytree as PT -import maia.transfer.protocols as EP -from maia.utils import np_utils, par_utils, as_pdm_gnum -from maia.utils.parallel import algo as par_algo -from maia.algo.dist import transform as dist_transform -from maia.algo.dist.merge_ids import merge_distributed_ids -from maia.algo.dist.remove_element import remove_elts_from_pl -from maia.algo.dist.subset_tools import vtx_ids_to_face_ids - -import numpy as np - -import Pypdm.Pypdm as PDM - -DIM_TO_LOC = {0:'Vertex', - 1:'EdgeCenter', - 2:'FaceCenter', - 3:'CellCenter'} -CGNS_TO_LOC = {'BAR_2' :'EdgeCenter', - 'TRI_3' :'FaceCenter', - 'TETRA_4':'CellCenter'} -LOC_TO_CGNS = {'EdgeCenter':'BAR_2', - 'FaceCenter':'TRI_3', - 'CellCenter':'TETRA_4'} - -def duplicate_specified_vtx(zone, vtx_pl, comm): - """ - Duplicate vtx specified in `vtx_pl` from a distributed zone. Vertex base FlowSolution_t - are duplicated as well. - Size of zone and Vertex distribution are updated - - Note : if an id appear twice in vtx_pl, it will be duplicated twice. This is because - data are added to the rank requesting the vertices in vtx_pl - """ - distri_n = PT.maia.getDistribution(zone, 'Vertex') - distri = PT.get_value(distri_n) - dn_vtx = distri[1] - distri[0] # Initial number of vertices - n_vtx = distri[2] - - # Update GridCoordinates - coords_keys = ['CoordinateX', 'CoordinateY', 'CoordinateZ'] - coord_nodes = {key: PT.get_node_from_name(zone, key) for key in coords_keys} - coords = {key: PT.get_value(node) for key, node in coord_nodes.items()} - new_coords = EP.block_to_part(coords, distri, [vtx_pl], comm) - for key in coords_keys: - PT.set_value(coord_nodes[key], np.concatenate([coords[key], new_coords[key][0]])) - - # Update FlowSolution - is_loc_fs = lambda n: PT.get_label(n)=='FlowSolution_t' and PT.Subset.GridLocation(n)=='Vertex' - for fs_n in PT.get_children_from_predicate(zone, is_loc_fs): - assert PT.get_child_from_name(fs_n, 'PointList') is None, "Partial FS are not supported" - - arrays_n = PT.get_children_from_label(fs_n, 'DataArray_t') - arrays = {PT.get_name(array_n) : PT.get_value(array_n) for array_n in arrays_n} - new_arrays = EP.block_to_part(arrays, distri, [vtx_pl], comm) - for array_n in arrays_n: - key = PT.get_name(array_n) - PT.set_value(array_n, np.concatenate([arrays[key], new_arrays[key][0]])) - - # Update distribution and zone size - PT.get_value(zone)[0][0] += comm.allreduce(vtx_pl.size, op=MPI.SUM) - add_distri = par_utils.dn_to_distribution(vtx_pl.size, comm) - new_distri = distri+add_distri - PT.set_value(distri_n, new_distri) - - # > Replace added vertices at the end of array - old_gnum = np.arange( distri[0], distri[1])+1 - new_gnum = np.arange(n_vtx+add_distri[0],n_vtx+add_distri[1])+1 - vtx_gnum = np.concatenate([old_gnum, new_gnum]) - - # Update GridCoordinates - coords_keys = ['CoordinateX', 'CoordinateY', 'CoordinateZ'] - coord_nodes = {key: PT.get_node_from_name(zone, key) for key in coords_keys} - coords = {key: [PT.get_value(node)] for key, node in coord_nodes.items()} - new_coords = EP.part_to_block(coords, new_distri, [vtx_gnum], comm) - for key in coords_keys: - PT.set_value(coord_nodes[key], new_coords[key]) - - # Update FlowSolution - is_loc_fs = lambda n: PT.get_label(n)=='FlowSolution_t' and PT.Subset.GridLocation(n)=='Vertex' - for fs_n in PT.get_children_from_predicate(zone, is_loc_fs): - assert PT.get_child_from_name(fs_n, 'PointList') is None, "Partial FS are not supported" - - arrays_n = PT.get_children_from_label(fs_n, 'DataArray_t') - arrays = {PT.get_name(array_n) : [PT.get_value(array_n)] for array_n in arrays_n} - new_arrays = EP.part_to_block(arrays, new_distri, [vtx_gnum], comm) - for array_n in arrays_n: - key = PT.get_name(array_n) - PT.set_value(array_n, new_arrays[key]) - -def remove_specified_vtx(zone, vtx_pl, comm): - """ - Remove vtx specified in `vtx_pl` from a distributed zone. Vertex base FlowSolution_t - are removed as well. - Size of zone and Vertex distribution are updated - - Note : id can appear twice in vtx_pl, it will be removed only once - """ - distri_n = PT.maia.getDistribution(zone, 'Vertex') - distri = PT.get_value(distri_n) - dn_vtx = distri[1] - distri[0] # Initial number of vertices - - # Get ids to remove - ptb = EP.PartToBlock(distri, [vtx_pl], comm) - ids = ptb.getBlockGnumCopy()-distri[0]-1 - - # Update GridCoordinates - for grid_co_n in PT.get_children_from_predicate(zone, 'GridCoordinates_t'): - for da_n in PT.get_children_from_label(grid_co_n, 'DataArray_t'): - old_val = PT.get_value(da_n) - PT.set_value(da_n, np.delete(old_val, ids)) - - # Update FlowSolution - is_loc_fs = lambda n: PT.get_label(n)=='FlowSolution_t' and PT.Subset.GridLocation(n)=='Vertex' - for fs_n in PT.get_children_from_predicate(zone, is_loc_fs): - assert PT.get_child_from_name(fs_n, 'PointList') is None, "Partial FS are not supported" - for da_n in PT.get_children_from_label(fs_n, 'DataArray_t'): - old_val = PT.get_value(da_n) - PT.set_value(da_n, np.delete(old_val, ids)) - - # Update distribution and zone size - PT.get_value(zone)[0][0] -= comm.allreduce(ids.size, op=MPI.SUM) - PT.set_value(distri_n, par_utils.dn_to_distribution(dn_vtx - ids.size, comm)) - - -def elmt_pl_to_vtx_pl(zone, elt_n, elt_pl, comm): - ''' - Return distributed gnum of vertices describing elements tagged in `elt_pl`. - ''' - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - - elt_size = PT.Element.NVtx(elt_n) - elt_offset = PT.Element.Range(elt_n)[0] - elt_distri = PT.maia.getDistribution(elt_n, 'Element')[1] - - # > Get partitionned connectivity of elt_pl - elt_ec = PT.get_value(PT.get_child_from_name(elt_n, 'ElementConnectivity')) - ids = elt_pl - elt_offset +1 - _, pl_ec = EP.block_to_part_strided(elt_size, elt_ec, elt_distri, [ids], comm) - - # > Get distributed vertices gnum referenced in pl_ec - ptb = EP.PartToBlock(vtx_distri, pl_ec, comm) - vtx_pl = ptb.getBlockGnumCopy() - - return vtx_pl - - -def tag_elmt_owning_vtx(elt_n, vtx_pl, comm, elt_full=False): - ''' - Return the point_list of elements that owns one or all of their vertices in the vertex point_list. - Important : elt_pl is returned as a distributed array, w/o any assumption on the holding - rank : vertices given by a rank can spawn an elt_idx on a other rank. - ''' - if elt_n is not None: - elt_offset = PT.Element.Range(elt_n)[0] - gc_elt_pl = vtx_ids_to_face_ids(vtx_pl, elt_n, comm, elt_full)+elt_offset-1 - else: - gc_elt_pl = np.empty(0, dtype=vtx_pl.dtype) - return gc_elt_pl - -def find_shared_faces(tri_elt, tri_pl, tetra_elt, tetra_pl, comm): - """ - For the given TRI and TETRA elements node, search the TRI faces shared - by the TETRA elements. In addition, TRI and TETRA can be filtered - with tri_pl and tetra_pl, which are distributed - cgnslike list of indices. - """ - # TRI elts - # Get ec - src_distri = PT.maia.getDistribution(tri_elt, 'Element')[1] - size_src_elt = PT.Element.NVtx(tri_elt) - src_ec = PT.get_child_from_name(tri_elt, 'ElementConnectivity')[1] - # Get list of TRI faces to select from other ranks - ptb = EP.PartToBlock(src_distri, [tri_pl - PT.Element.Range(tri_elt)[0] + 1], comm) - src_dist_gnum= ptb.getBlockGnumCopy() + PT.Element.Range(tri_elt)[0] - 1 - src_dist_ids = ptb.getBlockGnumCopy() - src_distri[0] - 1 - # Extract connectivity - src_ec_idx = np_utils.interweave_arrays([size_src_elt*src_dist_ids+i_size for i_size in range(size_src_elt)]) - src_ec_elt = src_ec[src_ec_idx] - - # TETRA elts - # Get ec - tgt_distri = PT.maia.getDistribution(tetra_elt, 'Element')[1] - size_tgt_elt = PT.Element.NVtx(tetra_elt) - tgt_ec = PT.get_child_from_name(tetra_elt, 'ElementConnectivity')[1] - # Get list of TETRA elts to select from other ranks - ptb = EP.PartToBlock(tgt_distri, [tetra_pl - PT.Element.Range(tetra_elt)[0] + 1], comm) - tgt_dist_ids = ptb.getBlockGnumCopy() - tgt_distri[0] - 1 - # Extract connectivity - tgt_ec_idx = np_utils.interweave_arrays([size_tgt_elt*tgt_dist_ids+i_size for i_size in range(size_tgt_elt)]) - tgt_ec_elt = tgt_ec[tgt_ec_idx] - - - # Tri are already decomposed - src_face_vtx_idx, src_face_vtx = 3*np.arange(src_dist_ids.size+1, dtype=np.int32), src_ec_elt - # Do tetra decomposition, locally - tgt_face_vtx_idx, tgt_face_vtx = PDM.decompose_std_elmt_faces(PDM._PDM_MESH_NODAL_TETRA4, as_pdm_gnum(tgt_ec_elt)) - - # Now we we will reuse seq algorithm is_unique_strided to find - # shared faces. Since they can be on different processes, we need to gather it - # (according to their sum of vtx) before. - # A fully distributded version of is_unique_strided would be better ... - tri_key = np.add.reduceat(src_face_vtx, src_face_vtx_idx[:-1]) - tetra_key = np.add.reduceat(tgt_face_vtx, tgt_face_vtx_idx[:-1]) - - weights = [np.ones(t.size, float) for t in [tri_key, tetra_key]] - ptb = EP.PartToBlock(None, [tri_key, tetra_key], comm, weight=weights, keep_multiple=True) - cst_stride = [np.ones(t.size-1, np.int32) for t in [src_face_vtx_idx, tgt_face_vtx_idx]] - - # Origin is not mandatory for TETRA because we just want the TRI ids at the end - _, origin = ptb.exchange_field([src_dist_gnum, np.zeros(tetra_key.size, src_dist_gnum.dtype)], part_stride=cst_stride) - _, tmp_ec = ptb.exchange_field([src_face_vtx, tgt_face_vtx], part_stride=[3*s for s in cst_stride]) - mask = np_utils.is_unique_strided(tmp_ec, 3, method='hash') - - mask[origin == 0] = True # We dont want to get tetra faces - out = origin[~mask] - - return out - - -def update_elt_vtx_numbering(zone, elt_n, old_to_new_vtx, comm, elt_pl=None): - ''' - Update element connectivity (partialy if `elt_pl` provided) according to the new vertices numbering described in `old_to_new_vtx`. - ''' - if elt_n is not None: - ec_n = PT.get_child_from_name(elt_n, 'ElementConnectivity') - ec = PT.get_value(ec_n) - - if elt_pl is None: - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - ec = EP.block_to_part(old_to_new_vtx, vtx_distri, [ec], comm)[0] - else: - elt_size = PT.Element.NVtx(elt_n) - elt_offset = PT.Element.Range(elt_n)[0] - elt_distri = PT.maia.getDistribution(elt_n, 'Element')[1] - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - - elt_pl_shft = elt_pl - elt_offset +1 - ptb = EP.PartToBlock(elt_distri, [elt_pl_shft], comm) - ids = ptb.getBlockGnumCopy()-elt_distri[0]-1 - ec_ids = np_utils.interweave_arrays([elt_size*ids+i_size for i_size in range(elt_size)]) - new_num_ec = EP.block_to_part(old_to_new_vtx, vtx_distri, [ec[ec_ids]], comm)[0] - ec[ec_ids] = new_num_ec - - PT.set_value(ec_n, ec) - - -def apply_offset_to_elts(zone, offset, min_range): - ''' - Go through all elements with ElementRange>min_range, applying offset to their ElementRange. - ''' - # > Add offset to elements with ElementRange>min_range - elt_nodes = PT.Zone.get_ordered_elements(zone) - for elt_n in elt_nodes: - elt_range = PT.Element.Range(elt_n) - if elt_range[0] > min_range: - elt_range += offset - - # > Treating all BCs outside of elts because if not elt of dim of BC, - # it wont be treated. - for bc_n in PT.get_nodes_from_predicates(zone, 'ZoneBC_t/BC_t'): - pl = PT.get_child_from_name(bc_n, 'PointList')[1][0] - pl[min_range Update Vertex BCs and GCs - update_vtx_bnds(zone, old_to_new_vtx, comm) - - # > Remove coordinates and FS + udpate zone dim - remove_specified_vtx(zone, pbc2_vtx_pl, comm) - - return old_to_new_vtx - - -def update_vtx_bnds(zone, old_to_new_vtx, comm): - ''' - Update Vertex BCs and GCs according to the new vertices numbering described in `old_to_new_vtx`. - TODO: predicates - ''' - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - - zone_bc_n = PT.get_child_from_label(zone, 'ZoneBC_t') - if zone_bc_n is not None: - is_vtx_bc = lambda n: PT.get_label(n)=='BC_t' and\ - PT.Subset.GridLocation(n)=='Vertex' - for bc_n in PT.get_children_from_predicate(zone_bc_n, is_vtx_bc): - bc_pl_n = PT.get_child_from_name(bc_n, 'PointList') - bc_pl = PT.get_value(bc_pl_n)[0] - bc_pl = EP.block_to_part(old_to_new_vtx, vtx_distri, [bc_pl], comm)[0] - assert (bc_pl!=-1).all() - PT.set_value(bc_pl_n, bc_pl.reshape((1,-1), order='F')) - - zone_gc_n = PT.get_child_from_label(zone, 'ZoneGridConnectivity_t') - if zone_gc_n is not None: - is_vtx_gc = lambda n: PT.get_label(n) in ['GridConnectivity1to1_t', 'GridConnectivity_t'] and\ - PT.Subset.GridLocation(n)=='Vertex' - for gc_n in PT.get_children_from_predicate(zone_gc_n, is_vtx_gc): - gc_pl_n = PT.get_child_from_name(gc_n, 'PointList') - gc_pl = PT.get_value(gc_pl_n)[0] - gc_pl = EP.block_to_part(old_to_new_vtx, vtx_distri, [gc_pl], comm)[0] - assert (gc_pl!=-1).all() - PT.set_value(gc_pl_n, gc_pl.reshape((1,-1), order='F')) - - gc_pld_n = PT.get_child_from_name(gc_n, 'PointListDonor') - gc_pld = PT.get_value(gc_pld_n)[0] - gc_pld = EP.block_to_part(old_to_new_vtx, vtx_distri, [gc_pld], comm)[0] - assert (gc_pld!=-1).all() - PT.set_value(gc_pld_n, gc_pld.reshape((1,-1), order='F')) - - -def duplicate_elts(zone, elt_n, elt_pl, as_bc, elts_to_update, comm, elt_duplicate_bcs=[]): - ''' - Duplicate elements tagged in `elt_pl` by updating its ElementConnectivity and ElementRange nodes, - as well as ElementRange nodes of elements with inferior dimension (assuming that element nodes are organized with decreasing dimension order). - Created elements can be tagged in a new BC_t through `as_bc` argument. - Elements of other dimension touching vertices of duplicated elements can be updating with new created vertices through `elts_to_update` argument. - BCs fully described by duplicated element vertices can be duplicated as well using `elt_duplicate_bcs` argument (lineic BCs in general). - ''' - # > Add duplicated vertex - n_vtx = PT.Zone.n_vtx(zone) - elt_vtx_pl = elmt_pl_to_vtx_pl(zone, elt_n, elt_pl, comm) - n_vtx_to_add = elt_vtx_pl.size - - duplicate_specified_vtx(zone, elt_vtx_pl, comm) - - new_vtx_distri = par_utils.dn_to_distribution(n_vtx_to_add, comm) - new_vtx_pl = np.arange(n_vtx+new_vtx_distri[0],n_vtx+new_vtx_distri[1], dtype=elt_vtx_pl.dtype)+1 - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - new_vtx_num = [elt_vtx_pl,new_vtx_pl] - - new_vtx_pl = EP.part_to_block([new_vtx_pl], vtx_distri, [elt_vtx_pl], comm) - ptb = EP.PartToBlock(vtx_distri, [elt_vtx_pl], comm) - elt_vtx_ids = ptb.getBlockGnumCopy()-vtx_distri[0]-1 - - old_to_new_vtx = np.arange(vtx_distri[0],vtx_distri[1], dtype=vtx_distri.dtype)+1 - old_to_new_vtx[elt_vtx_ids] = new_vtx_pl - - # > Add duplicated elements - n_elt = PT.Element.Size(elt_n) - elt_size = PT.Element.NVtx(elt_n) - elt_offset = PT.Element.Range(elt_n)[0] - elt_dim = PT.Element.Dimension(elt_n) - elt_distri_n = PT.maia.getDistribution(elt_n, distri_name='Element') - elt_distri = PT.get_value(elt_distri_n) - - ec_n = PT.get_child_from_name(elt_n, 'ElementConnectivity') - ec = PT.get_value(ec_n) - - # > Copy elements connectivity - elt_pl_shft = elt_pl - elt_offset +1 - ptb = EP.PartToBlock(elt_distri, [elt_pl_shft], comm) - ids = ptb.getBlockGnumCopy()-elt_distri[0]-1 - n_elt_to_add = ids.size - ec_ids = np_utils.interweave_arrays([elt_size*ids+i_size for i_size in range(elt_size)]) - duplicated_ec = EP.block_to_part(old_to_new_vtx, vtx_distri, [ec[ec_ids]], comm)[0] - new_ec = np.concatenate([ec, duplicated_ec]) - - # > Update element distribution - add_elt_distri = par_utils.dn_to_distribution(n_elt_to_add, comm) - new_elt_distri = elt_distri+add_elt_distri - PT.set_value(elt_distri_n, new_elt_distri) - - # > Update ElementConnectivity, by adding new elements at the end of distribution - n_elt = elt_distri[2] - old_gnum = np.arange( elt_distri[0], elt_distri[1])+1 - new_gnum = np.arange(n_elt+add_elt_distri[0],n_elt+add_elt_distri[1])+1 - elt_gnum = np.concatenate([old_gnum, new_gnum]) - - cst_stride = np.full(elt_gnum.size, elt_size, np.int32) - ptb = EP.PartToBlock(new_elt_distri, [elt_gnum], comm) - _, new_ec = ptb.exchange_field([new_ec], part_stride=[cst_stride]) - - PT.set_value(ec_n, new_ec) - - # > Update ElementRange - er = PT.Element.Range(elt_n) - er[1] += add_elt_distri[2] - - apply_offset_to_elts(zone, add_elt_distri[2], er[1]-add_elt_distri[2]) - - # > Create associated BC if asked - if as_bc is not None: - zone_bc_n = PT.get_child_from_label(zone, 'ZoneBC_t') - new_elt_pl = np.arange(n_elt+add_elt_distri[0], - n_elt+add_elt_distri[1], dtype=elt_distri.dtype) + elt_offset - bc_n = PT.new_BC(name=as_bc, - type='FamilySpecified', - point_list=new_elt_pl.reshape((1,-1), order='F'), - loc=DIM_TO_LOC[elt_dim], - family='PERIODIC', - parent=zone_bc_n) - PT.maia.newDistribution({'Index':add_elt_distri}, parent=bc_n) - - n_tri_added_tot = add_elt_distri[2] - - # > Duplicate twin BCs - new_ec = list() - twin_elt_bc_pl = list() - - # > Get element infos - is_asked_elt = lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='BAR_2' - elt_n = PT.get_node_from_predicate(zone, is_asked_elt) - if elt_duplicate_bcs != []: - assert elt_n is not None - n_elt = PT.Element.Size(elt_n) - elt_size = PT.Element.NVtx(elt_n) - elt_offset = PT.Element.Range(elt_n)[0] - elt_dim = PT.Element.Dimension(elt_n) - elt_distri_n = PT.maia.getDistribution(elt_n, 'Element') - elt_distri = PT.get_value(elt_distri_n) - - ec_n = PT.get_child_from_name(elt_n, 'ElementConnectivity') - ec = PT.get_value(ec_n) - - add_elt_distri = np.zeros(3, dtype=elt_distri.dtype) - new_elt_distri = elt_distri - new_gnum = list() - for matching_bcs in elt_duplicate_bcs: - bc_n = PT.get_child_from_name(zone_bc_n, matching_bcs[0]) - bc_pl = PT.get_value(PT.Subset.getPatch(bc_n))[0] - twin_elt_bc_pl.append(bc_pl) - - bc_pl_shft = bc_pl - elt_offset +1 - ptb = EP.PartToBlock(elt_distri, [bc_pl_shft], comm) - ids = ptb.getBlockGnumCopy()-elt_distri[0]-1 - n_elt_to_add_l = ids.size - ec_ids = np_utils.interweave_arrays([elt_size*ids+i_size for i_size in range(elt_size)]) - new_bc_ec = EP.block_to_part(old_to_new_vtx, vtx_distri, [ec[ec_ids]], comm)[0] - new_ec.append(new_bc_ec) - - # > Compute element distribution - add_elt_distri_l = par_utils.dn_to_distribution(n_elt_to_add_l, comm) - add_elt_distri = add_elt_distri+add_elt_distri_l - new_elt_distri = new_elt_distri+add_elt_distri_l - - new_bc_pl = np.arange(n_elt+add_elt_distri_l[0], - n_elt+add_elt_distri_l[1], dtype=bc_pl.dtype) - new_gnum_l= new_bc_pl + add_elt_distri[2] - add_elt_distri_l[2]+1 - new_bc_pl = new_bc_pl + elt_offset + add_elt_distri[2] - add_elt_distri_l[2] - - new_gnum.append(new_gnum_l) - new_bc_n = PT.new_BC(name=matching_bcs[1], - type='FamilySpecified', - point_list=new_bc_pl.reshape((1,-1), order='F'), - loc=DIM_TO_LOC[elt_dim], - parent=zone_bc_n) - PT.maia.newDistribution({'Index':add_elt_distri_l}, parent=new_bc_n) - - bc_fam_n = PT.get_child_from_label(bc_n, 'FamilyName_t') - if bc_fam_n is not None: - PT.new_FamilyName(PT.get_value(bc_fam_n), parent=new_bc_n) - - # > Update ElementConnectivity, by adding new elements at the end of distribution - new_ec = np.concatenate([ec]+new_ec) - - old_gnum = np.arange(elt_distri[0],elt_distri[1])+1 - new_gnum = np.concatenate(new_gnum) - elt_gnum = np.concatenate([old_gnum, new_gnum]) - - cst_stride = np.full(elt_gnum.size, elt_size, np.int32) - ptb = EP.PartToBlock(new_elt_distri, [elt_gnum], comm) - _, new_ec = ptb.exchange_field([new_ec], part_stride=[cst_stride]) - - PT.set_value(ec_n, new_ec) - - # > Update ElementRange - er = PT.Element.Range(elt_n) - er[1] += add_elt_distri[2] - - # > Update distribution - PT.set_value(elt_distri_n, new_elt_distri) - - # > Update vtx numbering of elements in patch to separate patch - cell_pl = elts_to_update['TETRA_4'] - face_pl = elts_to_update['TRI_3'] - line_pl = elts_to_update['BAR_2']+n_tri_added_tot # Attention au décalage de la PL - - # > Exclude constraint surf or both will move - tag_face = par_algo.gnum_isin(face_pl, elt_pl, comm) - face_pl = face_pl[~tag_face] - bc_line_pl = np.concatenate(twin_elt_bc_pl) if len(twin_elt_bc_pl)!=0 else np.empty(0, dtype=int) - tag_line = par_algo.gnum_isin(line_pl, bc_line_pl, comm) - line_pl = line_pl[~tag_line] - - tet_n = PT.get_child_from_predicate(zone, lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TETRA_4') - tri_n = PT.get_child_from_predicate(zone, lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TRI_3') - bar_n = PT.get_child_from_predicate(zone, lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='BAR_2') - - update_elt_vtx_numbering(zone, tet_n, old_to_new_vtx, comm, elt_pl=cell_pl) - update_elt_vtx_numbering(zone, tri_n, old_to_new_vtx, comm, elt_pl=face_pl) - update_elt_vtx_numbering(zone, bar_n, old_to_new_vtx, comm, elt_pl=line_pl) - - return new_vtx_num - - -def find_matching_bcs(zone, elt_n, src_pl, tgt_pl, src_tgt_vtx, comm): - ''' - Find pairs of twin BCs (lineic in general) using a matching vtx table. - ''' - # Steps: - # 1. Create a old->new table for vtx, using gc information - # 2. Pre filter BCs : keep the one appearing 100% in src_pl or tgt_pl. - # 3. For these bcs : find vertices ids in BAR, and requested vtx new id with old->new - # 4. Then, compare all arrays to find src / tgt matches - - matching_bcs = list() - - elt_dim = PT.Element.Dimension(elt_n) - is_elt_bc = lambda n: PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n)==DIM_TO_LOC[elt_dim] - - # > Compute new vtx numbering merging vtx from `src_tgt_vtx` - # Maybe there will be an issue in axisym because of vtx in both GCs - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - old_to_new_vtx = merge_distributed_ids(vtx_distri, src_tgt_vtx[0], src_tgt_vtx[1], comm, False) - - # > Find BCs described by element pls - bc_nodes = [list(),list()] - for bc_n in PT.get_nodes_from_predicate(zone, is_elt_bc, depth=2): - bc_pl = PT.get_child_from_name(bc_n, 'PointList')[1][0] - for i_side, elt_pl in enumerate([src_pl, tgt_pl]): - mask = par_algo.gnum_isin(bc_pl, elt_pl, comm) - if par_utils.all_true([mask], lambda t:t.all(), comm): - bc_nodes[i_side].append(bc_n) - - # > Get element infos - elt_offset = PT.Element.Range(elt_n)[0] - elt_size = PT.Element.NVtx(elt_n) - elt_ec = PT.get_value(PT.get_child_from_name(elt_n, 'ElementConnectivity')) - elt_distri = PT.maia.getDistribution(elt_n, 'Element')[1] - - # > Precompute vtx in shared numbering - bc_vtx = [list(),list()] - for i_side, _bc_nodes in enumerate(bc_nodes): - for src_bc_n in _bc_nodes: - bc_pl = PT.get_child_from_name(src_bc_n, 'PointList')[1][0] - - # > Get BC connectivity - ptb = EP.PartToBlock(elt_distri, [bc_pl-elt_offset+1], comm) - ids = ptb.getBlockGnumCopy()-elt_distri[0]-1 - ec_idx = np_utils.interweave_arrays([elt_size*ids+i_size for i_size in range(elt_size)]) - this_bc_vtx = elt_ec[ec_idx] # List of vertices belonging to bc - - # > Set BC connectivity in vtx shared numbering - vtx_ptb = EP.PartToBlock(vtx_distri, [this_bc_vtx], comm) - vtx_ids = vtx_ptb.getBlockGnumCopy() - bc_vtx_renum = EP.block_to_part(old_to_new_vtx, vtx_distri, [vtx_ids], comm) # Numbering of these vertices in shared numerotation - - bc_vtx[i_side].append(bc_vtx_renum[0]) - - # > Perfom comparaisons - for src_bc_n, src_bc_vtx in zip(bc_nodes[0], bc_vtx[0]): - for tgt_bc_n, tgt_bc_vtx in zip(bc_nodes[1], bc_vtx[1]): - tgt_vtx_tag = par_algo.gnum_isin(tgt_bc_vtx, src_bc_vtx, comm) - if par_utils.all_true([tgt_vtx_tag], lambda t:t.all(), comm): - matching_bcs.append([PT.get_name(tgt_bc_n), PT.get_name(src_bc_n)]) - - return matching_bcs - - -def add_undefined_faces(zone, elt_n, elt_pl, tgt_elt_n, comm): - ''' - Decompose `elt_pl` tetra faces (which are triangles), adding those that are not already - defined in zone and not defined by two tetras. - ''' - # > Get element infos - elt_size = PT.Element.NVtx(elt_n) - elt_offset = PT.Element.Range(elt_n)[0] - ec_n = PT.get_child_from_name(elt_n, 'ElementConnectivity') - ec = PT.get_value(ec_n) - elt_name = PT.Element.CGNSName(elt_n) - elt_distri = PT.maia.getDistribution(elt_n, 'Element')[1] - assert elt_name=='TETRA_4' - - tgt_elt_size = PT.Element.NVtx(tgt_elt_n) - tgt_elt_offset = PT.Element.Range(tgt_elt_n)[0] - tgt_ec_n = PT.get_child_from_name(tgt_elt_n, 'ElementConnectivity') - tgt_ec = PT.get_value(tgt_ec_n) - tgt_elt_name = PT.Element.CGNSName(tgt_elt_n) - tgt_elt_distri_n = PT.maia.getDistribution(tgt_elt_n, distri_name='Element') - tgt_elt_distri = PT.get_value(tgt_elt_distri_n) - assert tgt_elt_name=='TRI_3' - - # > Get TETRA_4 elt_pl connectivity - elt_pl_shft = elt_pl - elt_offset +1 - ptb = EP.PartToBlock(elt_distri, [elt_pl_shft], comm) - ids = ptb.getBlockGnumCopy()-elt_distri[0]-1 - ec_pl = np_utils.interweave_arrays([elt_size*ids+i_size for i_size in range(elt_size)]) # np_utils.multi_arange(idx*elt_size, (idx+1)*elt_size) seems not to be as quick - ec_elt = ec[ec_pl] - - # > Decompose tetra faces - tgt_face_vtx_idx, tgt_elt_ec = PDM.decompose_std_elmt_faces(PDM._PDM_MESH_NODAL_TETRA4, as_pdm_gnum(ec_elt)) - n_elt_to_add = tgt_face_vtx_idx.size-1 - - # > Find faces not already defined in TRI_3 connectivity or duplicated - tmp_ec = np.concatenate([tgt_elt_ec, tgt_ec]) - l_mask = par_algo.is_unique_strided(tmp_ec, tgt_elt_size, comm) - elt_ids = np.where(l_mask[0:n_elt_to_add])[0] - - n_elt_to_add = elt_ids.size - ec_pl = np_utils.interweave_arrays([tgt_elt_size*elt_ids+i_size for i_size in range(tgt_elt_size)]) - tgt_elt_ec = tgt_elt_ec[ec_pl] - - # > Update distribution - add_elt_distri = par_utils.dn_to_distribution(n_elt_to_add, comm) - new_elt_distri = tgt_elt_distri+add_elt_distri - PT.set_value(tgt_elt_distri_n, new_elt_distri) - - # > Update target element - tgt_ec_n = PT.get_child_from_name(tgt_elt_n, 'ElementConnectivity') - tgt_ec = PT.get_value(tgt_ec_n) - tgt_new_ec = np.concatenate([tgt_ec, tgt_elt_ec]) - - # > Replace added elements at the end of distribution - tgt_n_elt = tgt_elt_distri[2] - old_gnum = np.arange( tgt_elt_distri[0], tgt_elt_distri[1])+1 - new_gnum = np.arange(tgt_n_elt+add_elt_distri[0],tgt_n_elt+add_elt_distri[1])+1 - elt_gnum = np.concatenate([old_gnum, new_gnum]) - - cst_stride = np.full(elt_gnum.size, tgt_elt_size, np.int32) - ptb = EP.PartToBlock(new_elt_distri, [elt_gnum], comm) - _, tgt_new_ec = ptb.exchange_field([tgt_new_ec], part_stride=[cst_stride]) - PT.set_value(tgt_ec_n, tgt_new_ec) - - tgt_er = PT.Element.Range(tgt_elt_n) - tgt_er[1] += add_elt_distri[2] - - apply_offset_to_elts(zone, add_elt_distri[2], tgt_er[1]-add_elt_distri[2]) - - return new_gnum+tgt_elt_offset-1 - - -def convert_vtx_gcs_as_face_bcs(tree, comm): - ''' - Convert 1to1, periodic, vertex GCs as FaceCenter BCs for feflo. - Note : if a face is also present in a BC, then ??? - ''' - is_tri_elt = lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TRI_3' - is_face_bc = lambda n: PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n)=='FaceCenter' - is_per_gc = lambda n: PT.get_label(n)=='GridConnectivity_t' and PT.GridConnectivity.is1to1(n) and PT.GridConnectivity.isperiodic(n) - - for zone in PT.get_all_Zone_t(tree): - # > Get TRI_3 element infos - elt_n = PT.get_child_from_predicate(zone, is_tri_elt) - elt_offset = PT.Element.Range(elt_n)[0] - - # > Use BC PLs to detect BC elts that will be duplicated (cf issue JMarty) - face_bc_pls = [PT.get_value(PT.get_child_from_name(bc_n, 'PointList'))[0] - for bc_n in PT.get_nodes_from_predicate(zone, is_face_bc, depth=2)] - face_bc_pls = np.concatenate(face_bc_pls)-elt_offset+1 - - zone_bc_n = PT.get_node_from_label(zone, 'ZoneBC_t') - for gc_n in PT.iter_nodes_from_predicate(zone, is_per_gc, depth=2): - gc_name = PT.get_name(gc_n) - gc_pl = PT.get_value(PT.get_child_from_name(gc_n, 'PointList'))[0] - gc_loc = PT.Subset.GridLocation(gc_n) - assert gc_loc=='Vertex' - - # > Search faces described by gc vtx - bc_pl = maia.algo.dist.subset_tools.vtx_ids_to_face_ids(gc_pl, elt_n, comm, True) - mask = ~par_algo.gnum_isin(bc_pl, face_bc_pls, comm) - - bc_pl = bc_pl[mask]+elt_offset-1 - - if comm.allreduce(bc_pl.size, op=MPI.SUM)!=0: - bc_n = PT.new_BC(name=gc_name, - type='FamilySpecified', - point_list=bc_pl.reshape((1,-1), order='F'), - loc='FaceCenter', - family='GCS', - parent=zone_bc_n) - PT.maia.newDistribution({'Index' : par_utils.dn_to_distribution(bc_pl.size, comm)}, bc_n) - - -def deplace_periodic_patch(tree, jn_pairs, comm): - ''' - Use gc_paths and their associated periodic_values in `jn_pairs_and_values` to deplace a range of cell touching - a GC next to its twin GC (for each pair of GCs), using GCs pl and pld to merge the two domains. - This function is not working on domains where some vertices are shared by GCs (from different pairs or not). - ''' - base = PT.get_child_from_label(tree, 'CGNSBase_t') - - zones = PT.get_all_Zone_t(base) - assert len(zones)==1 - zone = zones[0] - - # > Add GCs as BCs (paraview visu mainly) - PT.new_Family('PERIODIC', parent=base) - PT.new_Family('GCS', parent=base) - - elts = PT.get_nodes_from_label(zone, 'Elements_t') - tri_elts = [elt for elt in elts if PT.Element.CGNSName(elt)=='TRI_3'] - tetra_elts = [elt for elt in elts if PT.Element.CGNSName(elt)=='TETRA_4'] - bar_elts = [elt for elt in elts if PT.Element.CGNSName(elt)=='BAR_2'] - assert len(tri_elts) == len(tetra_elts) == 1, f"Multiple elts nodes are not managed" - assert len(bar_elts) <= 1, f"Multiple elts nodes are not managed" - tri_elt = tri_elts[0] - tetra_elt = tetra_elts[0] - bar_elt = bar_elts[0] if len(bar_elts) > 0 else None - - new_vtx_nums = list() - to_constrain_bcs = list() - matching_bcs = list() - for i_per, gc_paths in enumerate(jn_pairs): - - gc_vtx_n = PT.get_node_from_path(tree, gc_paths[0]) - gc_vtx_pl = PT.get_value(PT.get_child_from_name(gc_vtx_n, 'PointList' ))[0] - gc_vtx_pld = PT.get_value(PT.get_child_from_name(gc_vtx_n, 'PointListDonor'))[0] - - # > 1/ Defining the internal surface, that will be constrained in mesh adaptation - cell_pl = tag_elmt_owning_vtx(tetra_elt, gc_vtx_pld, comm, elt_full=False) # Tetra made of at least one gc opp vtx - face_pl = add_undefined_faces(zone, tetra_elt, cell_pl, tri_elt, comm) # ? - vtx_pl = elmt_pl_to_vtx_pl(zone, tetra_elt, cell_pl, comm) # Vertices ids of tetra belonging to cell_pl - - zone_bc_n = PT.get_child_from_label(zone, 'ZoneBC_t') - cell_bc_name = f'tetra_4_periodic_{i_per}' - new_bc_distrib = par_utils.dn_to_distribution(cell_pl.size, comm) - bc_n = PT.new_BC(name=cell_bc_name, - type='FamilySpecified', - point_list=cell_pl.reshape((1,-1), order='F'), - loc='CellCenter', - family='PERIODIC', - parent=zone_bc_n) - PT.maia.newDistribution({'Index':new_bc_distrib}, parent=bc_n) - - face_bc_name = f'tri_3_constraint_{i_per}' - new_bc_distrib = par_utils.dn_to_distribution(face_pl.size, comm) - bc_n = PT.new_BC(name=face_bc_name, - type='FamilySpecified', - point_list=face_pl.reshape((1,-1), order='F'), - loc='FaceCenter', - family='PERIODIC', - parent=zone_bc_n) - PT.maia.newDistribution({'Index':new_bc_distrib}, parent=bc_n) - to_constrain_bcs.append(face_bc_name) - - # maia.io.dist_tree_to_file(tree, f'OUTPUT/internal_surface_{i_per}.cgns', comm) - - # > 2/ Removing lines defined on join because they surely has their periodic on the other side - # > Find BCs on GCs that will be deleted because they have their periodic twin - # > For now only fully described BCs will be treated - if bar_elt is not None: - bar_to_rm_pl = tag_elmt_owning_vtx(bar_elt, gc_vtx_pld, comm, elt_full=True) #Bar made of two gc opp vtx - bar_twins_pl = tag_elmt_owning_vtx(bar_elt, gc_vtx_pl , comm, elt_full=True) #Bar made of two gc vtx - _matching_bcs =find_matching_bcs(zone, bar_elt, bar_to_rm_pl, bar_twins_pl, [gc_vtx_pld, gc_vtx_pl], comm) - remove_elts_from_pl(zone, bar_elt, bar_to_rm_pl, comm) - else: - _matching_bcs = list() - matching_bcs.append(_matching_bcs) - - - # > 3/ Duplicate elts and vtx defined on internal created surface, and updating vtx numbering - # of elmts touching this surface - # > Defining which element related to created surface must be updated - to_update_cell_pl = cell_pl - to_update_face_pl = tag_elmt_owning_vtx(tri_elt, vtx_pl, comm, elt_full=True) - to_update_line_pl = tag_elmt_owning_vtx(bar_elt, vtx_pl, comm, elt_full=True) - - # > Ambiguous faces that contains all vtx but are not included in patch cells can be removed - to_update_face_pl = find_shared_faces(tri_elt, to_update_face_pl, tetra_elt, cell_pl, comm) - - elts_to_update = {'TETRA_4': to_update_cell_pl, 'TRI_3':to_update_face_pl, 'BAR_2':to_update_line_pl} - - face_bc_name = f'tri_3_periodic_{i_per}' - new_vtx_num = duplicate_elts(zone, tri_elt, face_pl, f'tri_3_periodic_{i_per}', elts_to_update, comm) - to_constrain_bcs.append(face_bc_name) - - - # > 4/ Apply periodic transformation to vtx and flowsol - vtx_pl = elmt_pl_to_vtx_pl(zone, tetra_elt, cell_pl, comm) - perio_val = PT.GridConnectivity.periodic_values(PT.get_node_from_path(tree, gc_paths[1])) - periodic = perio_val.asdict(snake_case=True) - - dist_transform.transform_affine_zone(zone, vtx_pl, comm, **periodic, apply_to_fields=True) - - # > 5/ Merge two GCs that are now overlaping - bc_name1 = PT.path_tail(gc_paths[0]) - bc_name2 = PT.path_tail(gc_paths[1]) - vtx_match_num = [gc_vtx_pl, gc_vtx_pld] - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - vtx_tag = np.arange(vtx_distri[0], vtx_distri[1], dtype=vtx_distri.dtype)+1 - old_to_new_vtx = merge_periodic_bc(zone, (bc_name1, bc_name2), vtx_tag, vtx_match_num, comm, keep_original=True) - - fake_vtx_distri = par_utils.dn_to_distribution(old_to_new_vtx.size, comm) - for i_previous_per in range(0, i_per): - new_vtx_nums[i_previous_per][0] = EP.block_to_part(old_to_new_vtx, fake_vtx_distri, [new_vtx_nums[i_previous_per][0]], comm)[0] - new_vtx_nums[i_previous_per][1] = EP.block_to_part(old_to_new_vtx, fake_vtx_distri, [new_vtx_nums[i_previous_per][1]], comm)[0] - - new_vtx_num[0] = EP.block_to_part(old_to_new_vtx, fake_vtx_distri, [new_vtx_num[0]], comm)[0] - new_vtx_num[1] = EP.block_to_part(old_to_new_vtx, fake_vtx_distri, [new_vtx_num[1]], comm)[0] - new_vtx_nums.append(new_vtx_num) - - # > Set Vertex BC to preserve join infos - pl_constraint = new_vtx_num[0].reshape((1,-1), order='F') - new_bc_distrib = par_utils.dn_to_distribution(pl_constraint.size, comm) - bc_n = PT.new_BC(name=f'vtx_constraint_{i_per}', - type='FamilySpecified', - point_list=pl_constraint, - loc='Vertex', - family='PERIODIC', - parent=zone_bc_n) - PT.maia.newDistribution({'Index':new_bc_distrib}, parent=bc_n) - - pl_periodic = new_vtx_num[1].reshape((1,-1), order='F') - new_bc_distrib = par_utils.dn_to_distribution(pl_periodic.size, comm) - bc_n = PT.new_BC(name=f'vtx_periodic_{i_per}', - type='FamilySpecified', - point_list=pl_periodic, - loc='Vertex', - family='PERIODIC', - parent=zone_bc_n) - PT.maia.newDistribution({'Index':new_bc_distrib}, parent=bc_n) - - return new_vtx_nums, to_constrain_bcs, matching_bcs - - -def retrieve_initial_domain(tree, jn_pairs_and_values, new_vtx_num, bcs_to_retrieve, comm): - ''' - Use `gc_paths` and their associated `periodic_values` to deplace the adapted range of cells that have been adapted to its initial position, - using `new_vtx_num` match info to merge the two domains. - Twin BCs defined in GCs that have been deleted while using `deplace_periodic_patch` can be retrieved thanks to `bcs_to_retrieve`. - ''' - n_periodicity = len(jn_pairs_and_values.keys()) - - base = PT.get_child_from_label(tree, 'CGNSBase_t') - is_3d = PT.get_value(base)[0]==3 - - zone = PT.get_child_from_label(base, 'Zone_t') - zone_bc_n = PT.get_child_from_label(zone, 'ZoneBC_t') - - elts = PT.get_nodes_from_label(zone, 'Elements_t') - bar_elts = [elt for elt in elts if PT.Element.CGNSName(elt)=='BAR_2'] - tri_elts = [elt for elt in elts if PT.Element.CGNSName(elt)=='TRI_3'] - tetra_elts = [elt for elt in elts if PT.Element.CGNSName(elt)=='TETRA_4'] - assert len(tri_elts) == len(tetra_elts) == 1, f"Multiple elts nodes are not managed" - assert len(bar_elts) <= 1, f"Multiple elts nodes are not managed" - tri_elt = tri_elts[0] - tetra_elt = tetra_elts[0] - bar_elt = bar_elts[0] if len(bar_elts) > 0 else None - - cell_elt_name = 'TETRA_4' if is_3d else 'TRI_3' - face_elt_name = 'TRI_3' if is_3d else 'BAR_2' - edge_elt_name = 'BAR_2' if is_3d else None - - # > Removing old periodic patch - i_per = n_periodicity-1 - for gc_paths, periodic_values in reversed(list(jn_pairs_and_values.items())): # reversed for future multiperiodic (use list() for py 3.7 support) - - # > 1/ Get elts and vtx for BCs out of feflo - # TODO: some TRI_3 should be avoided with find_shared_faces again - cell_bc_name = f'tetra_4_periodic_{i_per}' - cell_bc_n = PT.get_child_from_name(zone_bc_n, cell_bc_name) - cell_bc_pl = PT.get_value(PT.Subset.getPatch(cell_bc_n))[0] - vtx_pl = elmt_pl_to_vtx_pl(zone, tetra_elt, cell_bc_pl, comm) - - still_here_gc_name = gc_paths[0].split('/')[-1] - to_retrieve_gc_name = gc_paths[1].split('/')[-1] - bc_n = PT.get_child_from_name(zone_bc_n, still_here_gc_name) - face_pl = PT.get_value(PT.Subset.getPatch(bc_n))[0] - - # > Defining which element related to created surface must be updated - to_update_cell_pl = cell_bc_pl - to_update_face_pl = tag_elmt_owning_vtx(tri_elt, vtx_pl, comm, elt_full=True) - to_update_line_pl = tag_elmt_owning_vtx(bar_elt, vtx_pl, comm, elt_full=True) - to_update_face_pl = find_shared_faces(tri_elt, to_update_face_pl, tetra_elt, cell_bc_pl, comm) - elts_to_update = {'TETRA_4': to_update_cell_pl, 'TRI_3':to_update_face_pl, 'BAR_2':to_update_line_pl} - - # > 2/ Duplicate GC surface and update element connectivities in the patch - duplicate_elts(zone, tri_elt, face_pl, to_retrieve_gc_name, elts_to_update, comm, elt_duplicate_bcs=bcs_to_retrieve[i_per]) - - # > 3/ Deplace periodic patch to retrieve initial domain - # (vtx_pl is updated because has changed with surface duplication) - cell_bc_name = f'tetra_4_periodic_{i_per}' - bc_n = PT.get_child_from_name(zone_bc_n, cell_bc_name) - cell_pl = PT.get_value(PT.Subset.getPatch(bc_n))[0] - vtx_pl = elmt_pl_to_vtx_pl(zone, tetra_elt, cell_pl, comm) - periodic = periodic_values[0].asdict(True) - dist_transform.transform_affine_zone(zone, vtx_pl, comm, **periodic, apply_to_fields=True) - # maia.io.write_tree(tree, f'OUTPUT/adapted_and_deplaced_{i_per}.cgns') - - # > 4-5/ Merge two constraint surfaces - vtx_tag_n = PT.get_node_from_name(zone, 'vtx_tag') - vtx_tag = PT.get_value(vtx_tag_n) - _ = merge_periodic_bc(zone, - [f'{face_elt_name.lower()}_constraint_{i_per}', f'{face_elt_name.lower()}_periodic_{i_per}'], - vtx_tag, - new_vtx_num[i_per], comm) - i_per -=1 - - rm_feflo_added_elt(zone, comm) - - -def rm_feflo_added_elt(zone, comm): - ''' - Remove BCs that are created by mesh adaptation with feflo. - If BC location is not CellCenter the elements tagged in BCs are removed. - ''' - zone_bc_n = PT.get_child_from_label(zone, 'ZoneBC_t') - feflo_bcs = PT.get_children_from_name(zone_bc_n, 'feflo_*') - - for bc_n in feflo_bcs: - if PT.Subset.GridLocation(bc_n)=='CellCenter': - PT.rm_child(zone_bc_n, bc_n) - else: - bc_loc = PT.Subset.GridLocation(bc_n) - bc_pl = PT.get_value(PT.get_child_from_name(bc_n, 'PointList'))[0] - is_asked_elt = lambda n: PT.get_label(n)=='Elements_t' and\ - PT.Element.CGNSName(n)==LOC_TO_CGNS[bc_loc] - elt_n = PT.get_child_from_predicate(zone, is_asked_elt) - if elt_n is not None: - remove_elts_from_pl(zone, elt_n, bc_pl, comm) diff --git a/maia/algo/dist/concat_nodes.py b/maia/algo/dist/concat_nodes.py deleted file mode 100644 index e14c6458..00000000 --- a/maia/algo/dist/concat_nodes.py +++ /dev/null @@ -1,132 +0,0 @@ -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import np_utils, par_utils -from maia.algo.dist import matching_jns_tools as MJT - -def concatenate_subset_nodes(nodes, comm, output_name='ConcatenatedNode', - additional_data_queries=[], additional_child_queries=[], master=None): - """ - Concatenate some subset nodes (ie nodes having a PointList) into a single one. - Subset nodes to be merged shall describe the same entity (eg a BC that have been split in two parts) - - Name of the concatenated node can be specified using output_name argument - Concatenated array are PointList, PointListDonor, any DataArray_t + additional queries requested with - additional_data_queries argument. - Also copy the GridLocation node + all the nodes found using additional_child_queries - Query are understood starting from nodes[0], unless if an other node is given using master argument - Note that datas are only concatenated : duplicated, if any, are not removed. - """ - - if master is None: - master = nodes[0] - node = PT.new_node(output_name, PT.get_label(master), PT.get_value(master)) - - data_queries = additional_data_queries + ['PointList', 'PointListDonor', 'DataArray_t'] - for data_query in data_queries: - #Use master node to understand queries and collect nodes - for childs in PT.iter_children_from_predicates(master, data_query, ancestors=True): - path = '/'.join([PT.get_name(n) for n in childs]) - data_to_merge = [PT.get_node_from_path(node, path)[1] for node in nodes] - _, data_merged = np_utils.concatenate_np_arrays(data_to_merge) - - #Recreate structure (still using master infos) and add merged array - parent = node - for child in childs[:-1]: - parent = PT.update_child(parent, PT.get_name(child), PT.get_label(child), PT.get_value(child)) - child = childs[-1] - PT.new_child(parent, PT.get_name(child), PT.get_label(child), data_merged) - - #Copy child nodes - for child_query in ['GridLocation_t'] + additional_child_queries: - for child in PT.iter_children_from_predicates(master, child_query): - PT.add_child(node, child) - - newsize = PT.get_node_from_name(node, 'PointList')[1].shape[1] - idx_distri = PT.get_value(MT.getDistribution(master, 'Index')) - distri = np_utils.safe_int_cast(par_utils.gather_and_shift(newsize, comm), idx_distri.dtype) - MT.newDistribution({'Index' : distri[[comm.Get_rank(), comm.Get_rank()+1, comm.Get_size()]]}, node) - return node - -def concatenate_jns(tree, comm): - """ - Parse the GridConnectivity_t of a tree and concatenate the GCs related to a same zone: - if we have two jns A and B from zone1 to zone2 and two jns C and D from zone2 to zone1, - produce A' from zone1 to zone2 and B' from zone2 to zone1 - Periodic jns are merged if their Periodic node are the same - """ - match_jns = lambda n: PT.get_label(n) == 'GridConnectivity_t' and PT.GridConnectivity.is1to1(n) - - MJT.add_joins_donor_name(tree, comm) - for base, zone in PT.iter_children_from_predicates(tree, ['CGNSBase_t', 'Zone_t'], ancestors=True): - jns_to_merge = {'Vertex' : dict(), 'FaceCenter' : dict(), 'CellCenter' : dict()} - perio_refs = {'Vertex' : list(), 'FaceCenter' : list(), 'CellCenter' : list()} - #Do a get here because tree is modified - for zgc, jn in PT.get_children_from_predicates(zone, ['ZoneGridConnectivity_t', match_jns], ancestors=True): - donor_path = PT.GridConnectivity.ZoneDonorPath(jn, PT.get_name(base)) - location = PT.Subset.GridLocation(jn) - if location.endswith('FaceCenter'): - location = 'FaceCenter' # Map I,J,K FaceCenter to FaceCenter - perio_node = PT.get_child_from_label(jn, 'GridConnectivityProperty_t') - is_periodic = perio_node is not None - cur_jn_path = '/'.join([PT.get_name(node) for node in [base, zone, zgc, jn]]) - opp_jn_path = MJT.get_jn_donor_path(tree, cur_jn_path) - key = min(cur_jn_path, opp_jn_path) - - #Manage periodic -- merge only if periodic values are identical - if is_periodic: - found = False - for i,ref in enumerate(perio_refs[location]): - if PT.is_same_tree(perio_node, ref): - suffix = f'.P{i}' - found = True - break - if not found: - perio_refs[location].append(perio_node) - suffix = f'.P{len(perio_refs[location])-1}' - #Manage intrazone -- prevent merge of two sides into one - elif donor_path == PT.get_name(base) + '/' + PT.get_name(zone): - id = 0 if cur_jn_path < opp_jn_path else 1 - suffix = f'.I{id}' - else: - suffix = '' - donor_path = donor_path + suffix - - # Set opposite name here -- it will be transfered on merged node - if suffix == '.I0': opp_suffix = '.I1' - elif suffix == '.I1': opp_suffix = '.I0' - else: opp_suffix = suffix - opp_name_node = PT.get_child_from_name(jn, "GridConnectivityDonorName") - PT.set_value(opp_name_node, opp_jn_path.split('/')[1] + '.To.' + PT.get_name(zone) + opp_suffix) - - try: - jns_to_merge[location][donor_path].append((key,jn)) - except KeyError: - jns_to_merge[location][donor_path] = [(key,jn)] - PT.rm_child(zgc, jn) - - for location, ljns_to_merge in jns_to_merge.items(): - for donor_path, jns in ljns_to_merge.items(): - #We need to merge jn and opposite jn in same order so sort according to ordinal key - sorted_jns = [elem[1] for elem in sorted(jns)] - merged_name = PT.get_name(zone) + '.To.' + donor_path.split('/')[1] - merged = concatenate_subset_nodes(sorted_jns, comm, output_name=merged_name, - additional_child_queries=['GridConnectivityType_t', 'GridConnectivityProperty_t', 'Descriptor_t']) - PT.add_child(zgc, merged) - # Make name uniques if we have multiple GridLocation - if sum([len(ljns_to_merge) > 0 for ljns_to_merge in jns_to_merge.values()]) > 1: - loc_suffix = {'Vertex' : '_v', 'FaceCenter' : '_f', 'CellCenter' : '_c'} - for jn in PT.get_children_from_label(zgc, 'GridConnectivity_t'): - if len(PT.get_children_from_name(zgc, PT.get_name(jn))) > 1: - PT.set_name(jn, PT.get_name(jn) + '_' + PT.Subset.GridLocation(jn)[0]) - opp_name_node = PT.get_child_from_name(jn, "GridConnectivityDonorName") - PT.set_value(opp_name_node, PT.get_value(opp_name_node) + '_' + PT.Subset.GridLocation(jn)[0]) - # If we have multiple periodic jns or intrazone periodics, we can not guarantee that GridConnectivityDonorName is - # good so rebuild it - perio_found = False - for jn in PT.iter_children_from_predicates(tree, ['CGNSBase_t', 'Zone_t', 'ZoneGridConnectivity_t', match_jns]): - perio_found = PT.get_child_from_label(jn, 'GridConnectivityProperty_t') is not None - if perio_found: - break - if perio_found: - MJT.add_joins_donor_name(tree, comm, force=True) diff --git a/maia/algo/dist/conformize_jn.py b/maia/algo/dist/conformize_jn.py deleted file mode 100644 index b2f8c96b..00000000 --- a/maia/algo/dist/conformize_jn.py +++ /dev/null @@ -1,66 +0,0 @@ -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia.algo.dist.vertex_list as VL -from maia.transfer import protocols as EP - -def conformize_jn_pair(dist_tree, jn_paths, comm): - """ - Ensure that the vertices belonging to the two sides of a 1to1 GridConnectivity - have the same coordinates. - - Matching join with Vertex or FaceCenter location are admitted. Coordinates - of vertices are made equal by computing the arithmetic mean of the two - values. - - Input tree is modified inplace. - - Args: - dist_tree (CGNSTree): Input tree - jn_pathes (list of str): Pathes of the two matching ``GridConnectivity_t`` - nodes. Pathes must start from the root of the tree. - comm (`MPIComm`) : MPI communicator - - """ - coord_query = ['GridCoordinates_t', 'DataArray_t'] - - # Get vtx ids and opposite vtx ids for this join - location = PT.Subset.GridLocation(PT.get_node_from_path(dist_tree, jn_paths[0])) - if location == 'Vertex': - pl_vtx_list = [PT.get_node_from_path(dist_tree, jn_paths[0]+f'/PointList{d}')[1][0] for d in ['', 'Donor']] - elif location == 'FaceCenter': - pl_vtx_list = VL.generate_jn_vertex_list(dist_tree, jn_paths[0], comm)[:2] - else: - raise RuntimeError(f"Unsupported grid location for jn {jn_paths[0]}") - - # Collect data - mean_coords = {} - vtx_distris = [] - for i, path in enumerate(jn_paths): - zone = PT.get_node_from_path(dist_tree, PT.path_head(path, 2)) - vtx_distri = PT.get_value(MT.getDistribution(zone, 'Vertex')) - dist_coords = {} - for grid_co_n, coord_n in PT.iter_nodes_from_predicates(zone, coord_query, ancestors=True): - dist_coords[f"{PT.get_name(grid_co_n)}/{PT.get_name(coord_n)}"] = coord_n[1] - - part_coords = EP.block_to_part(dist_coords, vtx_distri, [pl_vtx_list[i]], comm) - - for path, value in part_coords.items(): - try: - mean_coords[path][0] += 0.5*value[0] - except KeyError: - mean_coords[path] = [0.5*value[0]] - vtx_distris.append(vtx_distri) - - # Send back the mean value to the two zones, and update tree - for i, path in enumerate(jn_paths): - zone = PT.get_node_from_path(dist_tree, PT.path_head(path, 2)) - mean_coords['NodeId'] = [pl_vtx_list[i]] - dist_data = EP.part_to_block(mean_coords, vtx_distris[i], [pl_vtx_list[i]], comm) - - loc_indices = dist_data.pop('NodeId') - vtx_distri[0] - 1 - - # Update data - for coord_path, value in dist_data.items(): - node = PT.get_node_from_path(zone, coord_path) - node[1][loc_indices] = value diff --git a/maia/algo/dist/connect_match.py b/maia/algo/dist/connect_match.py deleted file mode 100644 index 73343d79..00000000 --- a/maia/algo/dist/connect_match.py +++ /dev/null @@ -1,414 +0,0 @@ -from mpi4py import MPI - -import maia.pytree as PT -import maia.pytree.maia as MT -import Pypdm.Pypdm as PDM -import numpy as np -from maia.utils import np_utils, par_utils, as_pdm_gnum -from maia.utils.parallel import algo as par_algo -from maia.factory.partitioning.split_U import cgns_to_pdm_dmesh -from maia.transfer import protocols as EP - -from .subset_tools import convert_subset_as_facelist - -def _shift_face_num(cgns_ids, zone, reverse=False): - """ Shift CGNS face numbering to start at 1 """ - if PT.Zone.has_ngon_elements(zone): - offset = PT.Element.Range(PT.Zone.NGonNode(zone))[0] - 1 - else: - ordering = PT.Zone.elt_ordering_by_dim(zone) - if ordering == 1: #Increasing elements : substract starting point of 2D - offset = PT.Zone.get_elt_range_per_dim(zone)[2][0] - 1 - elif ordering == -1: #Decreasing elements : substract number of 3D - offset = PT.Zone.get_elt_range_per_dim(zone)[3][1] - else: - raise RuntimeError("Unable to extract unordered faces") - if reverse: - return cgns_ids + offset - else: - return cgns_ids - offset - -def _nodal_sections_to_face_vtx(sections, rank): - """ Rebuild a Ngon like connectivity (face_vtx) from sections coming from PDM """ - elem_n_vtx = lambda pdm_type : PT.Element.NVtx(PT.new_Elements(type=PT.maia.pdm_elts.pdm_elt_name_to_cgns_element_type(pdm_type))) - - face_n_vtx_list = [elem_n_vtx(section['pdm_type']) for section in sections] - - elem_dn_list = [section['np_distrib'][rank+1] - section['np_distrib'][rank] for section in sections] - - face_vtx_idx = np_utils.sizes_to_indices(np.repeat(face_n_vtx_list, elem_dn_list), dtype=np.int32) - _, face_vtx = np_utils.concatenate_np_arrays([section['np_connec'] for section in sections]) - return face_vtx_idx, face_vtx - -def _point_merge(clouds, comm, rel_tol): - """ - Wraps PDM.PointsMerge. A cloud is a tuple (coordinates, carac_lenght, parent_gnum) - Returns a dictionnary containing lgnum_cur and lgnum_opp which are paired (but in separated arrays) - and corresponding domains ids stored by pairs in np_cloup_pair - """ - pdm_point_merge = PDM.PointsMerge(comm, len(clouds), rel_tol) - - for icloud, cloud in enumerate(clouds): - coords = cloud['coords'] - carac_length = cloud['carac_length'] - dn_cloud_pts = carac_length.size - pdm_point_merge.cloud_set(icloud, dn_cloud_pts, coords, carac_length) - - pdm_point_merge.compute() - - return pdm_point_merge.make_interface() - -def _get_cloud(dmesh, gnum, comm): - """ - Extract surfacic mesh from a list of (face) gnum. Return connectivities - of extracted mesh + link with parent volumic mesh - """ - dmesh_extractor = PDM.DMeshExtract(2, comm) - if isinstance(dmesh, PDM.DistributedMesh): - dmesh_extractor.register_dmesh(dmesh) - elif isinstance(dmesh, PDM.DistributedMeshNodal): - dmesh_extractor.register_dmesh_nodal(dmesh) - - _gnum = as_pdm_gnum(gnum) - dmesh_extractor.set_gnum_to_extract(PDM._PDM_MESH_ENTITY_FACE, _gnum) - - dmesh_extractor.compute() - - if isinstance(dmesh, PDM.DistributedMesh): - dmesh_extracted = dmesh_extractor.get_dmesh() - coords = dmesh_extracted.dmesh_vtx_coord_get() - face_vtx_idx, face_vtx = dmesh_extracted.dmesh_connectivity_get(PDM._PDM_CONNECTIVITY_TYPE_FACE_VTX) - elif isinstance(dmesh, PDM.DistributedMeshNodal): - dmesh_extracted = dmesh_extractor.get_dmesh_nodal() - coords = dmesh_extracted.dmesh_nodal_get_vtx(comm)['np_vtx'] - # Rebuild face_vtx from sections - sections = dmesh_extracted.dmesh_nodal_get_sections(PDM._PDM_GEOMETRY_KIND_SURFACIC, comm)['sections'] - face_vtx_idx, face_vtx = _nodal_sections_to_face_vtx(sections, comm.Get_rank()) - - parent_vtx = dmesh_extractor.get_extract_parent_gnum(PDM._PDM_MESH_ENTITY_VTX) - parent_face = dmesh_extractor.get_extract_parent_gnum(PDM._PDM_MESH_ENTITY_FACE) - - carac_length = PDM.compute_vtx_characteristic_length(comm, - face_vtx_idx.size-1, #dn_face - 0, #dn_edge - coords.size//3, #dn_vtx - face_vtx_idx, - face_vtx, - None, #edge_vtx - coords) - - return {'coords' : coords, - 'carac_length' : carac_length, - 'face_vtx_idx' : face_vtx_idx, - 'face_vtx' : face_vtx, - 'parent_vtx' : parent_vtx, - 'parent_face' : parent_face} - - -def _convert_match_result_to_faces(out_vtx, clouds, comm): - """ - Translate the result of _point_merge (obtained at vertices) to the faces of the - surfacic 2d meshes - """ - - zones_dn_vtx = [cloud['parent_vtx'].size for cloud in clouds] - zones_dn_face = [cloud['parent_face'].size for cloud in clouds] - zones_face_vtx_idx = [cloud['face_vtx_idx'] for cloud in clouds] - zones_face_vtx = [cloud['face_vtx'] for cloud in clouds] - - n_interface = len(out_vtx['np_cloud_pair']) // 2 - interface_dn_vtx = [cur.size for cur in out_vtx['lgnum_cur']] - interface_ids_vtx = [np_utils.interweave_arrays([cur, opp]) for (cur, opp) in zip(out_vtx['lgnum_cur'], out_vtx['lgnum_opp'])] - interface_dom_vtx = [out_vtx['np_cloud_pair'][i:i+2] for i in range(0, len(out_vtx['np_cloud_pair']), 2)] - - - _out_face = PDM.interface_vertex_to_face(n_interface, - len(clouds), - False, - interface_dn_vtx, - interface_ids_vtx, - interface_dom_vtx, - zones_dn_vtx, - zones_dn_face, - zones_face_vtx_idx, - zones_face_vtx, - comm) - - - # Filter empty interfaces - is_empty_l = np.array([_out_face[j]['interface_dn_face'] == 0 for j in range(n_interface)], dtype=bool) - is_empty = np.empty(n_interface, bool) - comm.Allreduce(is_empty_l, is_empty, op=MPI.LAND) - - # Use same format for out_face - out_face = {'np_cloud_pair' : out_vtx['np_cloud_pair'][~np.repeat(is_empty, 2)], - 'lgnum_cur' : [np.absolute(_out_face[j]['np_interface_ids_face'][0::2]) for j in range(n_interface) if not is_empty[j]], - 'lgnum_opp' :[np.absolute(_out_face[j]['np_interface_ids_face'][1::2]) for j in range(n_interface) if not is_empty[j]]} - - # Result is parallelism dependant // Sort it - for i, (gnum, gnum_opp) in enumerate(zip(out_face['lgnum_cur'], out_face['lgnum_opp'])): - sorter = par_algo.DistSorter(gnum, comm) - out_face['lgnum_cur'][i] = sorter.sort(gnum) - out_face['lgnum_opp'][i] = sorter.sort(gnum_opp) - - return out_face - -def _clean_degenerated_vtx_interface(out_vtx, out_face): - """ - Remove vertex interface if the corresponding face interface is empty. This - also prevent jns of same side to be connected. - """ - vtx_pair = out_vtx['np_cloud_pair'] - face_pair = out_face['np_cloud_pair'] - # Retrieve interface having no faces (interface come in same order) - idx = 0 - is_empty = np.ones(vtx_pair.size//2, bool) - for view in np.split(face_pair, face_pair.size // 2): - while not (vtx_pair[2*idx:2*(idx+1)] == view).all(): - idx += 1 - is_empty[idx] = False - - # Filter - out_vtx['np_cloud_pair'] = out_vtx['np_cloud_pair'][~np.repeat(is_empty, 2)] - out_vtx['lgnum_cur'] = [data for b, data in zip(is_empty, out_vtx['lgnum_cur']) if not b] - out_vtx['lgnum_opp'] = [data for b, data in zip(is_empty, out_vtx['lgnum_opp']) if not b] - - -def get_vtx_cloud_from_subset(dist_tree, subset_path, comm, dmesh_cache={}): - """ - Wrapper extracting the surfacic meshes and parent data from the input tree - and a list of node paths. - Node path must refer to nodes having a FaceCenter PointList - """ - zone_path = PT.path_head(subset_path, 2) - zone = PT.get_node_from_path(dist_tree, zone_path) - try: - dmesh = dmesh_cache[zone_path] - except KeyError: - if PT.Zone.has_ngon_elements(zone): - dmesh = cgns_to_pdm_dmesh.cgns_dist_zone_to_pdm_dmesh(zone, comm) - else: - dmesh = cgns_to_pdm_dmesh.cgns_dist_zone_to_pdm_dmesh_nodal(zone, comm, needs_bc=False) - dmesh.generate_distribution() - dmesh_cache[zone_path] = dmesh - - node = PT.get_node_from_path(dist_tree, subset_path) - assert PT.Subset.GridLocation(node) == 'FaceCenter', "Only face center nodes are managed" - pl = PT.get_child_from_name(node, 'PointList')[1][0] - _pl = _shift_face_num(pl, zone) - - cloud = _get_cloud(dmesh, _pl, comm) - return cloud - -def apply_periodicity(cloud, periodic): - coords = cloud['coords'] - cx = coords[0::3] - cy = coords[1::3] - cz = coords[2::3] - cx_p, cy_p, cz_p = np_utils.transform_cart_vectors(cx,cy,cz, **periodic) - coords_p = np_utils.interweave_arrays([cx_p, cy_p, cz_p]) - cloud['coords'] = coords_p - - -def connect_1to1_from_paths(dist_tree, subset_paths, comm, periodic=None, **options): - - # Steps are - # 1. Get input PL at faces (if they are vertex -> convert it) - # 2. Extract 2d face mesh. Apply periodicity if necessary - # 3. Get matching result at vertex - # 4. Convert matching result at faces - # 5. Go back to volumic numbering - # 6. Create output for matched faces - # 7. Check resulting faces vs input faces - - assert len(subset_paths) == 2 - tol = options.get("tol", 1e-2) - output_loc = options.get("location", "FaceCenter") - - - clouds_path = subset_paths[0] + subset_paths[1] - clouds = [] - - for cloud_path in clouds_path: - convert_subset_as_facelist(dist_tree, cloud_path, comm) # Only ngon - - cached_dmesh = {} #Use caching to avoid translate zone->dmesh 2 times - for cloud_path in subset_paths[0]: - cloud = get_vtx_cloud_from_subset(dist_tree, cloud_path, comm, cached_dmesh) - if periodic is not None: - apply_periodicity(cloud, periodic) - clouds.append(cloud) - for cloud_path in subset_paths[1]: - cloud = get_vtx_cloud_from_subset(dist_tree, cloud_path, comm, cached_dmesh) - clouds.append(cloud) - - PT.rm_nodes_from_name(dist_tree, ":CGNS#MultiPart") #Cleanup - - matching_vtx = _point_merge(clouds, comm, tol) - matching_face = _convert_match_result_to_faces(matching_vtx, clouds, comm) - _clean_degenerated_vtx_interface(matching_vtx, matching_face) - - # Conversion en numéro parent - n_interface_vtx = len(matching_vtx['np_cloud_pair']) // 2 - n_interface_face = len(matching_face['np_cloud_pair']) // 2 - for i_itrf in range(n_interface_vtx): - for j, side in enumerate(['lgnum_cur', 'lgnum_opp']): - i_cloud = matching_vtx['np_cloud_pair'][2*i_itrf+j] - parent_vtx_num = clouds[i_cloud]['parent_vtx'] - distri_vtx = par_utils.dn_to_distribution(parent_vtx_num.size, comm) - matching_vtx[side][i_itrf] = EP.block_to_part(parent_vtx_num, distri_vtx, [matching_vtx[side][i_itrf]], comm)[0] - - for i_itrf in range(n_interface_face): - for j, side in enumerate(['lgnum_cur', 'lgnum_opp']): - i_cloud = matching_face['np_cloud_pair'][2*i_itrf+j] - parent_face_num = clouds[i_cloud]['parent_face'] - parent_zone = PT.get_node_from_path(dist_tree, PT.path_head(clouds_path[i_cloud], 2)) - distri_face = par_utils.dn_to_distribution(parent_face_num.size, comm) - gnum_2d = EP.block_to_part(parent_face_num, distri_face, [matching_face[side][i_itrf]], comm)[0] - # At this point face are in gnum but local to 2d dimension : shift back - matching_face[side][i_itrf] = _shift_face_num(gnum_2d, parent_zone, reverse=True) - - # Add created nodes in tree - if output_loc == 'Vertex': - cloud_pair = matching_vtx['np_cloud_pair'] - gnum_cur = matching_vtx['lgnum_cur'] - gnum_opp = matching_vtx['lgnum_opp'] - elif output_loc == 'FaceCenter': - cloud_pair = matching_face['np_cloud_pair'] - gnum_cur = matching_face['lgnum_cur'] - gnum_opp = matching_face['lgnum_opp'] - - if periodic is not None: - perio_opp = {'translation' : - periodic.get('translation', np.zeros(3, np.float32)), - 'rotation_center' : periodic.get('rotation_center', np.zeros(3, np.float32)), - 'rotation_angle' : - periodic.get('rotation_angle', np.zeros(3, np.float32))} - else: - perio_opp = None - - n_spawn = {path:0 for path in clouds_path} - for i_interface in range(cloud_pair.size // 2): - - jn_distri = par_utils.dn_to_distribution(gnum_cur[i_interface].size, comm) - for j in range(2): - if j == 0: - origin_path_cur = clouds_path[cloud_pair[2*i_interface]] - origin_path_opp = clouds_path[cloud_pair[2*i_interface+1]] - _gnum_cur = gnum_cur[i_interface] - _gnum_opp = gnum_opp[i_interface] - _periodic = periodic - else: - origin_path_cur = clouds_path[cloud_pair[2*i_interface+1]] - origin_path_opp = clouds_path[cloud_pair[2*i_interface]] - _gnum_cur = gnum_opp[i_interface] - _gnum_opp = gnum_cur[i_interface] - _periodic = perio_opp - - leaf_name_cur = PT.path_tail(origin_path_cur) - leaf_name_opp = PT.path_tail(origin_path_opp) - zone_cur_path = PT.path_head(origin_path_cur, 2) - zone_opp_path = PT.path_head(origin_path_opp, 2) - zone_cur = PT.get_node_from_path(dist_tree, zone_cur_path) - zgc = PT.update_child(zone_cur, 'ZoneGridConnectivity', 'ZoneGridConnectivity_t') - - jn_name_cur = f"{leaf_name_cur}_{n_spawn[origin_path_cur]}" - jn_name_opp = f"{leaf_name_opp}_{n_spawn[origin_path_opp]}" - - jn = PT.new_GridConnectivity(jn_name_cur, - zone_opp_path, - 'Abutting1to1', - loc=output_loc, - point_list = _gnum_cur.reshape((1,-1), order='F'), - point_list_donor = _gnum_opp.reshape((1,-1), order='F'), - parent=zgc) - PT.new_child(jn, "GridConnectivityDonorName", "Descriptor_t", jn_name_opp) - - if periodic is not None: - PT.new_GridConnectivityProperty(_periodic, jn) - - MT.newDistribution({"Index" : jn_distri.copy()}, jn) - - to_copy = lambda n: PT.get_label(n) in ['FamilyName_t', 'AdditionalFamilyName_t'] - origin_node = PT.get_node_from_path(dist_tree, origin_path_cur) - for node in PT.get_children_from_predicate(origin_node, to_copy): - PT.add_child(jn, node) - - n_spawn[origin_path_cur] += 1 - n_spawn[origin_path_opp] += 1 - - - # Cleanup : remove input node or transform it to keep only unmatched faces - for i_cloud, cloud_path in enumerate(clouds_path): - spawn = np.where(cloud_pair == i_cloud)[0] - itrf_id, side = np.divmod(spawn, 2) # Convert into num interface + pos (O or 1) - input_face = PT.get_node_from_path(dist_tree, f"{cloud_path}/PointList")[1][0] - output_faces = [] - for j,s in zip(itrf_id, side): - output_faces.append(matching_face[['lgnum_cur', 'lgnum_opp'][s]][j]) - # Search input_face that are not in output face - unfound = par_algo.dist_set_difference(input_face, output_faces, comm) - if comm.allreduce(unfound.size, MPI.SUM) > 0: - input_node = PT.get_node_from_path(dist_tree, cloud_path) - PT.set_name(input_node, f"{PT.get_name(input_node)}_unmatched") - PT.update_child(input_node, 'GridLocation', value='FaceCenter') - PT.update_child(input_node, 'PointList', value=unfound.reshape((1,-1), order='F')) - MT.newDistribution({'Index': par_utils.dn_to_distribution(unfound.size, comm)}, input_node) - else: - PT.rm_node_from_path(dist_tree, cloud_path) - - -def connect_1to1_families(dist_tree, families, comm, periodic=None, **options): - """Find the matching faces between cgns nodes belonging to the two provided families. - - For each one of the two families, all the BC_t or GridConnectivity_t nodes related to the family - through a FamilyName/AdditionalFamilyName node will be included in the pairing process. - These subset must have a Vertex or FaceCenter GridLocation. - - If the interface is periodic, the transformation from the first to the second family - entities must be specified using the ``periodic`` argument; a dictionnary with keys - ``'translation'``, ``'rotation_center'`` and/or ``'rotation_angle'`` (in radians) is expected. - Each key maps to a 3-sized numpy array, with missing keys defaulting zero vector. - - Input tree is modified inplace : relevant GridConnectivity_t with PointList and PointListDonor - data are created. - If all the original elements are successfully paired, the original nodes are removed. Otherwise, - unmatched faces remains in their original node which is suffixed by '_unmatched'. - - This function allows the additional optional parameters: - - - ``location`` (default = 'FaceCenter') -- Controls the output GridLocation of - the created interfaces. 'FaceCenter' or 'Vertex' are admitted. - - ``tol`` (default = 1e-2) -- Geometric tolerance used to pair two points. Note that for each vertex, this - tolerance is relative to the minimal distance to its neighbouring vertices. - - Args: - dist_tree (CGNSTree) : Input distributed tree. Only U connectivities are managed. - families (tuple of str): Name of the two families to connect. - comm (MPIComm): MPI communicator - periodic (dic, optional): Transformation from first to second family if the interface is periodic. - None otherwise. Defaults to None. - **options: Additional options - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #recover1to1@start - :end-before: #recover1to1@end - :dedent: 2 - """ - - is_subset_container = lambda n: PT.get_label(n) in ['ZoneBC_t', 'ZoneGridConnectivity_t'] - is_subset = lambda n: PT.get_label(n) in ['BC_t', 'GridConnectivity_t', 'GridConnectivity1to1_t'] - - assert isinstance(families, (list, tuple)) and len(families) == 2 - - subset_path = (list(), list()) - for zone_path in PT.predicates_to_paths(dist_tree, 'CGNSBase_t/Zone_t'): - zone = PT.get_node_from_path(dist_tree, zone_path) - for container in PT.get_children_from_predicate(zone, is_subset_container): - for subset in PT.get_children_from_predicate(container, is_subset): - path = f'{zone_path}/{PT.get_name(container)}/{PT.get_name(subset)}' - for i_fam, family in enumerate(families): - if PT.predicate.belongs_to_family(subset, family, True): - subset_path[i_fam].append(path) - - connect_1to1_from_paths(dist_tree, subset_path, comm, periodic, **options) - diff --git a/maia/algo/dist/dist_algo.pybind.cpp b/maia/algo/dist/dist_algo.pybind.cpp deleted file mode 100644 index cda99de5..00000000 --- a/maia/algo/dist/dist_algo.pybind.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "maia/algo/dist/dist_algo.pybind.hpp" -#if __cplusplus > 201703L -#include "cpp_cgns/interop/pycgns_converter.hpp" -#include "maia/utils/parallel/mpi4py.hpp" -#include "maia/__old/transform/convert_to_std_elements.hpp" -#include "maia/algo/dist/rearrange_element_sections/rearrange_element_sections.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.hpp" -#include "maia/algo/dist/elements_to_ngons/elements_to_ngons.hpp" -#include "maia/algo/dist/fsdm_distribution/fsdm_distribution.hpp" -#include "maia/__old/transform/put_boundary_first/put_boundary_first.hpp" -#include "maia/algo/dist/split_boundary_subzones_according_to_bcs/split_boundary_subzones_according_to_bcs.hpp" - -namespace py = pybind11; - -template auto -apply_cpp_cgns_function_to_py_base(F&& f) { - return [&f](py::list py_base) { - cgns::tree base = cgns::to_cpp_tree(py_base); - f(base); - update_py_tree(std::move(base),py_base); - }; -} -template auto -apply_cpp_cgns_par_function_to_py_base(F&& f) { - return [&f](py::list py_base, py::object mpi4py_comm) { - cgns::tree base = cgns::to_cpp_tree(py_base); - MPI_Comm comm = maia::mpi4py_comm_to_comm(mpi4py_comm); - f(base,comm); - update_py_tree(std::move(base),py_base); - }; -} - -const auto generate_interior_faces_and_parents = apply_cpp_cgns_par_function_to_py_base(maia::generate_interior_faces_and_parents); -const auto elements_to_ngons = apply_cpp_cgns_par_function_to_py_base(maia::elements_to_ngons); -const auto convert_zone_to_std_elements = apply_cpp_cgns_function_to_py_base(maia::convert_zone_to_std_elements); -const auto add_fsdm_distribution = apply_cpp_cgns_par_function_to_py_base(maia::add_fsdm_distribution); -const auto rearrange_element_sections = apply_cpp_cgns_par_function_to_py_base(maia::rearrange_element_sections); -const auto put_boundary_first = apply_cpp_cgns_par_function_to_py_base(maia::put_boundary_first); -const auto split_boundary_subzones_according_to_bcs = apply_cpp_cgns_par_function_to_py_base(maia::split_boundary_subzones_according_to_bcs); - - -void register_dist_algo_module(py::module_& parent) { - - py::module_ m = parent.def_submodule("dist_algo"); - - m.def("generate_interior_faces_and_parents" , generate_interior_faces_and_parents , "Generate TRI_3_interior and QUAD_4_interior element sections, and adds ParentElement to interior and exterior faces"); - m.def("elements_to_ngons" , elements_to_ngons , "Convert to NGon"); - m.def("convert_zone_to_std_elements" , convert_zone_to_std_elements , "ngon to elements"); - m.def("add_fsdm_distribution" , add_fsdm_distribution , "Add FSDM-specific distribution info"); - m.def("rearrange_element_sections" , rearrange_element_sections , "For a distributed base, merge Elements_t nodes of the same type and does the associated renumbering"); - m.def("put_boundary_first" , put_boundary_first , "ngon sorted with boundary faces first"); - m.def("split_boundary_subzones_according_to_bcs", split_boundary_subzones_according_to_bcs, "Split a ZoneSubRegion node with a PointRange spaning all boundary faces into multiple ZoneSubRegion with a BCRegionName"); - -} -#else //C++==17 - -namespace py = pybind11; -void register_dist_algo_module(py::module_& parent) { - - py::module_ m = parent.def_submodule("dist_algo"); - -} -#endif //C++>17 diff --git a/maia/algo/dist/dist_algo.pybind.hpp b/maia/algo/dist/dist_algo.pybind.hpp deleted file mode 100644 index eca75d27..00000000 --- a/maia/algo/dist/dist_algo.pybind.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#include - -void register_dist_algo_module(pybind11::module_& parent); - diff --git a/maia/algo/dist/duplicate.py b/maia/algo/dist/duplicate.py deleted file mode 100644 index 6e924b10..00000000 --- a/maia/algo/dist/duplicate.py +++ /dev/null @@ -1,262 +0,0 @@ -import numpy as np - -import maia.pytree as PT - -from maia.utils import py_utils -import maia.algo.transform as TRF -import maia.algo.dist.conformize_jn as CCJ -import maia.algo.dist.matching_jns_tools as MJT - -def duplicate_from_periodic_jns(dist_tree, zone_paths, jn_paths_for_dupl, dupl_nb, comm, - conformize=False, apply_to_fields = False): - """ - Function to duplicate n times a set of connected zones - > dist_tree : distributed tree from wich 'zones' come and in wich duplicated zones will be added - > zone_paths : list of pathes (BaseName/ZoneName) of the connected zones to duplicate - > jn_paths_for_dupl : list of 2 lists (listA,listB) where listA (resp listB) is the list - that contains all GridConnectivity nodes defining the first (resp - second) part of a periodic matching - > dupl_nb : is the number of duplication apply to 'zones' - > conformize : if True, compute the coordinates mean of each connected vertices and this mean replace - the previous coordinates for each vertices. In this case, the matching is perfect. - > comm : MPI communicator - > apply_to_fields : apply only the rotation to all vector fields in CGNS nodes of type : - "FlowSolution_t", "DiscreteData_t", "ZoneSubRegion_t", "BCDataset_t" - """ - - ############# - # Example for a monozone duplication - # - # If jn = [[Path/To/MatchA],[Path/To/MatchB]] - # Then first = MatchA and second = MatchB - # - # ________ ________________ - # | | | || | - # | | | || | - # | | | || | - # | Zone | ===>>> | Zone || Zone | - # | | | || dup | - # /| |\ /| || |\ - # / |______| \ / |______||______| \ - # / \ / / \ \ - # MatchA MatchB MatchA MatchB \ MatchBDup - # MatchADup - # - # ------------------------- - # Example for a multizones duplication - # - # If jn = [[Path/To/MatchA1,Path/To/MatchA2],[Path/To/MatchB1,Path/To/MatchB2]] - # Then first = MatchA1 and second = MatchB1 - # - # MatchA1Dup - # MatchA1 MatchB1 MatchA1 MatchB1 / MatchB1Dup - # \ _________ / \ ________\_/_______ / - # \ | | / \ | || | / - # \| Zone1 |/ \| Zone1 || Zone- |/ - # | | | || dup1 | - # |-------| ===>>> |-------||-------| - # | | | || Zone- | - # /| Zone2 |\ /| Zone2 || dup2 |\ - # / |_______| \ / |_______||_______| \ - # / \ / / \ \ - # MatchA2 MatchB2 MatchA2 MatchB2 \ MatchB2Dup - # MatchA2Dup - # - ############# - - if dupl_nb < 0: - return - - jn_paths_a, jn_paths_b = jn_paths_for_dupl - zones = [PT.get_node_from_path(dist_tree, path) for path in zone_paths] - - #Store initial values of joins - jn_values_a = [PT.get_value(PT.get_node_from_path(dist_tree,jn_path_a)) for jn_path_a in jn_paths_a] - jn_values_b = [PT.get_value(PT.get_node_from_path(dist_tree,jn_path_b)) for jn_path_b in jn_paths_b] - - # Prepare matching jns - if conformize: - jn_to_opp = {} - for i, jn_path_a in enumerate(jn_paths_a): - jn_path_b = MJT.get_jn_donor_path(dist_tree, jn_path_a) - assert jn_path_b in jn_paths_b - jn_to_opp[jn_path_a] = jn_path_b - - # Get first join in the first list of joins (A) - first_join_in_matchs_a = PT.get_node_from_path(dist_tree, jn_paths_a[0]) - - # Get transformation information - rotation_center_a, rotation_angle_a, translation_a = PT.GridConnectivity.periodic_values(first_join_in_matchs_a) - if rotation_angle_a.size == 2: - rotation_angle_a = rotation_angle_a[0] if rotation_angle_a[0] != 0 else rotation_angle_a[1] # We dont know if angle is stored in array[0] or array[1] - - # Store initial periodicity information of joins of the second joins list (B) - jn_b_properties = [] - for jn_path_b in jn_paths_b: - jn_b_init_node = PT.get_node_from_path(dist_tree, jn_path_b) - jn_b_property = PT.get_child_from_label(jn_b_init_node, "GridConnectivityProperty_t") - jn_b_properties.append(PT.deep_copy(jn_b_property)) - - # Get the name of all zones to duplicate in order to update the value of GridConnectivity - # nodes not involved in the duplication (not in jn_paths_for_dupl) - gc_values_to_update = zone_paths + [PT.get_name(zone) for zone in zones] #Manage both ways BaseName/ZoneName + ZoneName - - gc_predicate = ["ZoneGridConnectivity_t", - lambda n : PT.get_label(n) in ["GridConnectivity_t", "GridConnectivity1to1_t"]] - - # Update the value of all GridConnectivity nodes not involved in the duplication from initial zones - for zone_path, zone in zip(zone_paths, zones): - for zgc, gc in PT.iter_children_from_predicates(zone, gc_predicate, ancestors=True): - init_gc_path = f"{zone_path}/{PT.get_name(zgc)}/{PT.get_name(gc)}" - if (init_gc_path not in jn_paths_a) and (init_gc_path not in jn_paths_b): - gc_value = PT.get_value(gc) - if gc_value in gc_values_to_update: - PT.set_value(gc, f"{gc_value}.D0") - PT.set_name(zone, f"{PT.get_name(zone)}.D0") #Update zone name - - # Duplicate 'dupl_nb' times the list of zones 'zones' - for n in range(dupl_nb): - for zone_path, zone in zip(zone_paths, zones): - base_name, root_zone_name = zone_path.split('/') - base = PT.get_child_from_name(dist_tree, base_name) - duplicated_zone = PT.deep_copy(zone) - PT.set_name(duplicated_zone, f"{root_zone_name}.D{n+1}") - TRF.transform_affine(duplicated_zone, - rotation_center = rotation_center_a, - rotation_angle = (n+1)*rotation_angle_a, - translation = (n+1)*translation_a, - apply_to_fields = apply_to_fields) - - # Update the value of all GridConnectivity nodes not involved in the duplication from initial zones - for zgc, gc in PT.iter_children_from_predicates(duplicated_zone, gc_predicate, ancestors=True): - gc_path = f"{zone_path}/{PT.get_name(zgc)}/{PT.get_name(gc)}" - if (gc_path not in jn_paths_a) and (gc_path not in jn_paths_b): - gc_value = ".D0".join(PT.get_value(gc).split(".D0")[0:-1]) - if gc_value in gc_values_to_update: - PT.set_value(gc, f"{gc_value}.D{n+1}") - - # Add duplicated zone to the suitable base - PT.add_child(base, duplicated_zone) - - # Transform periodic joins of the second joins list (B) from previous set of zones - # to non periodic joins - for jb, jn_path_b in enumerate(jn_paths_b): - jn_path_b_prev = PT.update_path_elt(jn_path_b, 1, lambda zn : zn + f".D{n}") - jn_b_prev_node = PT.get_node_from_path(dist_tree, jn_path_b_prev) - PT.rm_children_from_label(jn_b_prev_node, "GridConnectivityProperty_t") - PT.set_value(jn_b_prev_node, f"{jn_values_b[jb]}.D{n+1}") - - # Transform periodic joins of the fisrt joins list (A) from current set of zones - # to non periodic joins - for ja, jn_path_a in enumerate(jn_paths_a): - jn_path_a_curr = PT.update_path_elt(jn_path_a, 1, lambda zn : zn + f".D{n+1}") - jn_a_curr_node = PT.get_node_from_path(dist_tree, jn_path_a_curr) - PT.rm_children_from_label(jn_a_curr_node, "GridConnectivityProperty_t") - PT.set_value(jn_a_curr_node, f"{jn_values_a[ja]}.D{n}") - - if conformize: - for jn_path_a, jn_path_b in jn_to_opp.items(): - jn_path_a_curr = PT.update_path_elt(jn_path_a, 1, lambda zn : zn + f".D{n+1}") - jn_path_b_prev = PT.update_path_elt(jn_path_b, 1, lambda zn : zn + f".D{n}") - CCJ.conformize_jn_pair(dist_tree, [jn_path_a_curr, jn_path_b_prev], comm) - - # Update information for joins of the fisrt joins list (A) from initial set of zones - for ja, jn_path_a in enumerate(jn_paths_a): - jn_path_a_init = PT.update_path_elt(jn_path_a, 1, lambda zn : zn + ".D0") - jn_a_init_node = PT.get_node_from_path(dist_tree, jn_path_a_init) - gcp_a_init = PT.get_child_from_label(jn_a_init_node, "GridConnectivityProperty_t") - rotation_angle_a_node = PT.get_node_from_name(gcp_a_init, "RotationAngle", depth=2) - translation_a_node = PT.get_node_from_name(gcp_a_init, "Translation", depth=2) - PT.set_value(rotation_angle_a_node, PT.get_value(rotation_angle_a_node) * (dupl_nb+1)) - PT.set_value(translation_a_node, PT.get_value(translation_a_node) * (dupl_nb+1)) - PT.set_value(jn_a_init_node, f"{jn_values_a[ja]}.D{dupl_nb}") - - # Update information for joins of the second joins list (B) from last set of duplicated zones - for jb, jn_path_b in enumerate(jn_paths_b): - jn_path_b_last = PT.update_path_elt(jn_path_b, 1, lambda zn : zn + f".D{dupl_nb}") - jn_b_last_node = PT.get_node_from_path(dist_tree, jn_path_b_last) - PT.rm_children_from_label(jn_b_last_node, 'GridConnectivityProperty_t') - PT.add_child(jn_b_last_node, jn_b_properties[jb]) - gcp_b_last = PT.get_child_from_label(jn_b_last_node, "GridConnectivityProperty_t") - rotation_angle_b_node = PT.get_node_from_name(gcp_b_last, "RotationAngle", depth=2) - translation_b_node = PT.get_node_from_name(gcp_b_last, "Translation", depth=2) - PT.set_value(rotation_angle_b_node, PT.get_value(rotation_angle_b_node) * (dupl_nb+1)) - PT.set_value(translation_b_node, PT.get_value(translation_b_node) * (dupl_nb+1)) - PT.set_value(jn_b_last_node, f"{jn_values_b[jb]}.D0") - - -def duplicate_from_rotation_jns_to_360(dist_tree, zone_paths, jn_paths_for_dupl, comm, - conformize=False, apply_to_fields=False): - """Reconstitute a circular mesh from an angular section of the geometry. - - Input tree is modified inplace. - - Args: - dist_tree (CGNSTree): Input distributed tree - zone_paths (list of str): List of pathes (BaseName/ZoneName) of the connected zones to duplicate - jn_paths_for_dupl (pair of list of str): (listA, listB) where listA (resp. list B) stores all the - pathes of the GridConnectivity nodes defining the first (resp. second) side of a periodic match. - comm (MPIComm) : MPI communicator - conformize (bool, optional): If true, ensure that the generated interface vertices have exactly same - coordinates (see :func:`conformize_jn_pair`). Defaults to False. - apply_to_fields (bool, optional): See :func:`maia.algo.transform_affine`. Defaults to False. - - """ - - if conformize: - jn_to_opp = {} - for i, jn_path_a in enumerate(jn_paths_for_dupl[0]): - jn_path_b = MJT.get_jn_donor_path(dist_tree, jn_path_a) - assert jn_path_b in jn_paths_for_dupl[1] - jn_to_opp[jn_path_a] = jn_path_b - _jn_paths_for_dupl = [ [], [] ] - for path, path_opp in jn_to_opp.items(): - _jn_paths_for_dupl[0].append(path) - _jn_paths_for_dupl[1].append(path_opp) - else: - _jn_paths_for_dupl = jn_paths_for_dupl - - # Get first join in the first list of joins (A) - first_join_in_matchs_a = PT.get_node_from_path(dist_tree, _jn_paths_for_dupl[0][0]) - - # Get transformation information - rotation_center_a, rotation_angle_a, translation_a = PT.GridConnectivity.periodic_values(first_join_in_matchs_a) - - if (translation_a != 0).any(): - raise ValueError("The join is not periodic only by rotation !") - - # Find the number of duplication needed - index = np.where(rotation_angle_a != 0)[0] - if index.size == 1: - sectors_number = abs(int(np.round(2*np.pi/rotation_angle_a[index]))) - rotation_angle_a[index] = np.sign(rotation_angle_a[index]) * 2*np.pi/sectors_number - else: - raise ValueError("Zone/Join not define a section of a row") - - if rotation_angle_a.size == 2: - rotation_angle_a = rotation_angle_a[0] if rotation_angle_a[0] != 0. else rotation_angle_a[1] - - # Duplicate 'sectors_number - 1' times the list of zones 'zones' - duplicate_from_periodic_jns(dist_tree, zone_paths, _jn_paths_for_dupl, sectors_number-1, - comm, conformize, apply_to_fields) - - # Transform periodic joins of the fisrt joins list (A) from initial set of zones - # to non periodic joins - for jn_path_a in _jn_paths_for_dupl[0]: - jn_path_a_init = PT.update_path_elt(jn_path_a, 1, lambda zn : zn + f".D{0}") - jn_a_init_node = PT.get_node_from_path(dist_tree, jn_path_a_init) - PT.rm_children_from_label(jn_a_init_node, "GridConnectivityProperty_t") - - # Transform periodic joins of the second joins list (B) from last set of duplicated zones - # to non periodic joins - for jn_path_b in _jn_paths_for_dupl[1]: - jn_path_b_last = PT.update_path_elt(jn_path_b, 1, lambda zn : zn + f".D{sectors_number-1}") - jn_b_last_node = PT.get_node_from_path(dist_tree, jn_path_b_last) - PT.rm_children_from_label(jn_b_last_node, "GridConnectivityProperty_t") - - if conformize: - # Conformize last, other have been conformized in duplicate_from_periodic_jns - for jn_path_a, jn_path_b in jn_to_opp.items(): - jn_path_a_init = PT.update_path_elt(jn_path_a, 1, lambda zn : zn + f".D{0}") - jn_path_b_last = PT.update_path_elt(jn_path_b, 1, lambda zn : zn + f".D{sectors_number-1}") - CCJ.conformize_jn_pair(dist_tree, [jn_path_a_init, jn_path_b_last], comm) diff --git a/maia/algo/dist/elements_to_ngons/__init__.py b/maia/algo/dist/elements_to_ngons/__init__.py deleted file mode 100644 index 688b61d5..00000000 --- a/maia/algo/dist/elements_to_ngons/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -import cmaia.dist_algo as cdist_algo -from maia.algo.apply_function_to_nodes import apply_to_bases,apply_to_zones - -from maia.utils import require_cpp20 - -@require_cpp20 -def generate_interior_faces_and_parents(dist_tree,comm): - apply_to_zones(cdist_algo.generate_interior_faces_and_parents, dist_tree, comm) - -@require_cpp20 -def elements_to_ngons(dist_tree,comm): - """ - Transform an element based connectivity into a polyedric (NGon based) - connectivity. - - The tree is modified in place: standard elements are removed from the zones - and PointLists are updated. - - Requirement: the ``Element_t`` nodes must be divided into: - first 2D element sections, then 3D element sections - - See details :ref:`here ` - - Args: - dist_tree (CGNSTree): Tree with an element-based connectivity - comm (`MPIComm`): MPI communicator - """ - apply_to_zones(cdist_algo.elements_to_ngons, dist_tree, comm) diff --git a/maia/algo/dist/elements_to_ngons/connectivity/element_faces.hpp b/maia/algo/dist/elements_to_ngons/connectivity/element_faces.hpp deleted file mode 100644 index abd14077..00000000 --- a/maia/algo/dist/elements_to_ngons/connectivity/element_faces.hpp +++ /dev/null @@ -1,105 +0,0 @@ -#pragma once - - -#include "cpp_cgns/sids/cgnslib.h" -#include "std_e/base/not_implemented_exception.hpp" - - -// SEE https://cgns.github.io/CGNS_docs_current/sids/conv.html#unst_3d -namespace maia { - - -template< - cgns::ElementType_t elt_type, - class connectivity_type, - class tri_iterator, class quad_iterator -> auto -generate_faces(const connectivity_type& e, tri_iterator& tri_it, quad_iterator& quad_it) -> void { - using namespace cgns; - if constexpr (elt_type==TRI_3) { - *tri_it++ = e; - } - else if constexpr (elt_type==QUAD_4) { - *quad_it++ = e; - } - else if constexpr (elt_type==TETRA_4) { - *tri_it++ = {e[0],e[2],e[1]}; - *tri_it++ = {e[0],e[1],e[3]}; - *tri_it++ = {e[1],e[2],e[3]}; - *tri_it++ = {e[2],e[0],e[3]}; - } - else if constexpr (elt_type==PYRA_5) { - *quad_it++ = {e[0],e[3],e[2],e[1]}; - * tri_it++ = {e[0],e[1],e[4]}; - * tri_it++ = {e[1],e[2],e[4]}; - * tri_it++ = {e[2],e[3],e[4]}; - * tri_it++ = {e[3],e[0],e[4]}; - } - else if constexpr (elt_type==PENTA_6) { - *quad_it++ = {e[0],e[1],e[4],e[3]}; - *quad_it++ = {e[1],e[2],e[5],e[4]}; - *quad_it++ = {e[2],e[0],e[3],e[5]}; - * tri_it++ = {e[0],e[2],e[1]}; - * tri_it++ = {e[3],e[4],e[5]}; - } - else if constexpr (elt_type==HEXA_8) { - *quad_it++ = {e[0],e[3],e[2],e[1]}; - *quad_it++ = {e[0],e[1],e[5],e[4]}; - *quad_it++ = {e[1],e[2],e[6],e[5]}; - *quad_it++ = {e[2],e[3],e[7],e[6]}; - *quad_it++ = {e[0],e[4],e[7],e[3]}; - *quad_it++ = {e[4],e[5],e[6],e[7]}; - } - else { - throw std_e::not_implemented_exception("unsupported ElementType_t "+to_string(elt_type)); - } -} - - -template< - cgns::ElementType_t elt_type, - class tri_iterator, class quad_iterator -> auto -generate_parent_positions(tri_iterator& tri_it, quad_iterator& quad_it) -> void { - using namespace cgns; - if constexpr (elt_type==TRI_3) { - *tri_it++ = 1; - } - else if constexpr (elt_type==QUAD_4) { - *quad_it++ = 1; - } - else if constexpr (elt_type==TETRA_4) { - *tri_it++ = 1; - *tri_it++ = 2; - *tri_it++ = 3; - *tri_it++ = 4; - } - else if constexpr (elt_type==PYRA_5) { - *quad_it++ = 1; - * tri_it++ = 2; - * tri_it++ = 3; - * tri_it++ = 4; - * tri_it++ = 5; - } - else if constexpr (elt_type==PENTA_6) { - *quad_it++ = 1; - *quad_it++ = 2; - *quad_it++ = 3; - * tri_it++ = 4; - * tri_it++ = 5; - } - else if constexpr (elt_type==HEXA_8) { - *quad_it++ = 1; - *quad_it++ = 2; - *quad_it++ = 3; - *quad_it++ = 4; - *quad_it++ = 5; - *quad_it++ = 6; - } - else { - throw std_e::not_implemented_exception("unsupported ElementType_t "+to_string(elt_type)); - } -} - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/connectivity/from_structured_grid.hpp b/maia/algo/dist/elements_to_ngons/connectivity/from_structured_grid.hpp deleted file mode 100644 index 1b1c2130..00000000 --- a/maia/algo/dist/elements_to_ngons/connectivity/from_structured_grid.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - - -#include "std_e/multi_index/fortran_order.hpp" -#include - - -namespace maia { - - -template auto -generate_hex_8(const Multi_index& vertex_dims, const Multi_index& is) -> std::array { - std::array hex; - hex[0] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1] ,is[2] } ); - hex[1] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0]+1,is[1] ,is[2] } ); - hex[2] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0]+1,is[1]+1,is[2] } ); - hex[3] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1]+1,is[2] } ); - hex[4] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1] ,is[2]+1} ); - hex[5] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0]+1,is[1] ,is[2]+1} ); - hex[6] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0]+1,is[1]+1,is[2]+1} ); - hex[7] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1]+1,is[2]+1} ); - return hex; -} - -template auto -generate_quad_4_normal_to(const Multi_index& vertex_dims, const Multi_index& is) -> std::array { - std::array quad; - static_assert(0<=d && d<3); - if constexpr (d==0) { - quad[0] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1] ,is[2] } ); - quad[1] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1]+1,is[2] } ); - quad[2] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1]+1,is[2]+1} ); - quad[3] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1] ,is[2]+1} ); - } else if constexpr (d==1) { - quad[0] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1] ,is[2] } ); - quad[1] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1] ,is[2]+1} ); - quad[2] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0]+1,is[1] ,is[2]+1} ); - quad[3] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0]+1,is[1] ,is[2] } ); - } else if constexpr (d==2) { - quad[0] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1] ,is[2] } ); - quad[1] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0]+1,is[1] ,is[2] } ); - quad[2] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0]+1,is[1]+1,is[2] } ); - quad[3] = 1 + std_e::fortran_order_from_dimensions( vertex_dims , Multi_index{is[0] ,is[1]+1,is[2] } ); - } - return quad; -} - - -template auto -generate_parent_cell_id(const Multi_index& cell_dims, const Multi_index& is) -> I { - return 1 + std_e::fortran_order_from_dimensions( cell_dims, is ); -} - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/connectivity/test/element_faces.test.cpp b/maia/algo/dist/elements_to_ngons/connectivity/test/element_faces.test.cpp deleted file mode 100644 index a6eef4ad..00000000 --- a/maia/algo/dist/elements_to_ngons/connectivity/test/element_faces.test.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - -#include "maia/algo/dist/elements_to_ngons/connectivity/element_faces.hpp" -#include "std_e/data_structure/block_range/block_range.hpp" -#include "cpp_cgns/sids.hpp" - -using namespace cgns; -using namespace maia; -using std::array; - -// SEE https://cgns.github.io/CGNS_docs_current/sids/conv.html#unst_3d -TEST_CASE("generate_faces") { - std::vector gen_tris; - auto tri_range = std_e::view_as_block_range(gen_tris); - auto tri_back = back_inserter(tri_range); - - std::vector gen_quads; - auto quad_range = std_e::view_as_block_range(gen_quads); - auto quad_back = back_inserter(quad_range); - - std::vector tri_pos; - auto tri_pos_back = back_inserter(tri_pos); - - std::vector quad_pos; - auto quad_pos_back = back_inserter(quad_pos); - - SUBCASE("tri_3") { - array tri = {1,2,3}; - - generate_faces(tri,tri_back,quad_back); - generate_parent_positions(tri_pos_back,quad_pos_back); - - CHECK( gen_tris == std::vector{1,2,3} ); - CHECK( gen_quads == std::vector{} ); - - CHECK( tri_pos == std::vector{1} ); - CHECK( quad_pos == std::vector{} ); - } - SUBCASE("quad_4") { - array quad = {1,2,3,4}; - - generate_faces(quad,tri_back,quad_back); - generate_parent_positions(tri_pos_back,quad_pos_back); - - CHECK( gen_tris == std::vector{} ); - CHECK( gen_quads == std::vector{1,2,3,4} ); - - CHECK( tri_pos == std::vector{} ); - CHECK( quad_pos == std::vector{1} ); - } - SUBCASE("tetra_4") { - array tet = {1,2,3,4}; - - generate_faces(tet,tri_back,quad_back); - generate_parent_positions(tri_pos_back,quad_pos_back); - - CHECK( gen_tris == std::vector{1,3,2, 1,2,4, 2,3,4, 3,1,4} ); - CHECK( gen_quads == std::vector{} ); - - CHECK( tri_pos == std::vector{1,2,3,4} ); - CHECK( quad_pos == std::vector{} ); - } - SUBCASE("pyra_5") { - array pyra = {1,2,3,4,5}; - - generate_faces(pyra,tri_back,quad_back); - generate_parent_positions(tri_pos_back,quad_pos_back); - - CHECK( gen_tris == std::vector{1,2,5, 2,3,5, 3,4,5, 4,1,5} ); - CHECK( gen_quads == std::vector{1,4,3,2} ); - - CHECK( tri_pos == std::vector{2,3,4,5} ); - CHECK( quad_pos == std::vector{1} ); - } - SUBCASE("penta_6") { - array penta = {1,2,3,4,5,6}; - - generate_faces(penta,tri_back,quad_back); - generate_parent_positions(tri_pos_back,quad_pos_back); - - CHECK( gen_tris == std::vector{1,3,2, 4,5,6} ); - CHECK( gen_quads == std::vector{1,2,5,4, 2,3,6,5, 3,1,4,6} ); - - CHECK( tri_pos == std::vector{4,5} ); - CHECK( quad_pos == std::vector{1,2,3} ); - } - SUBCASE("hexa_8") { - array hex = {1,2,3,4,5,6,7,8}; - - generate_faces(hex,tri_back,quad_back); - generate_parent_positions(tri_pos_back,quad_pos_back); - - CHECK( gen_tris == std::vector{} ); - CHECK( gen_quads == std::vector{1,4,3,2, 1,2,6,5, 2,3,7,6, 3,4,8,7, 1,5,8,4, 5,6,7,8} ); - - CHECK( tri_pos == std::vector{} ); - CHECK( quad_pos == std::vector{1,2,3,4,5,6} ); - } -} - -TEST_CASE("generate_faces: example with several connec") { - // prepare - std::vector gen_tris(6); - auto tri_range = std_e::view_as_block_range(gen_tris); - auto tri_it = tri_range.begin(); - - std::vector gen_quads(8); - auto quad_range = std_e::view_as_block_range(gen_quads); - auto quad_it = quad_range.begin(); - - std::vector tri_pos(2); - auto tri_pos_it = tri_pos.begin(); - - std::vector quad_pos(2); - auto quad_pos_it = quad_pos.begin(); - - // gen several connec - array tri0 = {10,11,12}; - generate_faces(tri0 ,tri_it,quad_it); - generate_parent_positions(tri_pos_it,quad_pos_it); - - array quad0 = {1,2,3,4}; - generate_faces(quad0,tri_it,quad_it); - generate_parent_positions(tri_pos_it,quad_pos_it); - - array quad1 = {4,3,2,1}; - generate_faces(quad1,tri_it,quad_it); - generate_parent_positions(tri_pos_it,quad_pos_it); - - array tri1 = {20,21,22}; - generate_faces(tri1 ,tri_it,quad_it); - generate_parent_positions(tri_pos_it,quad_pos_it); - - CHECK( gen_tris == std::vector{10,11,12, 20,21,22} ); - CHECK( gen_quads == std::vector{1,2,3,4, 4,3,2,1} ); - CHECK( tri_pos == std::vector{1,1} ); - CHECK( quad_pos == std::vector{1,1} ); -} -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/connectivity/test/from_structured_grid.test.cpp b/maia/algo/dist/elements_to_ngons/connectivity/test/from_structured_grid.test.cpp deleted file mode 100644 index 08755ba1..00000000 --- a/maia/algo/dist/elements_to_ngons/connectivity/test/from_structured_grid.test.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "std_e/unit_test/doctest.hpp" - -#include "maia/algo/dist/elements_to_ngons/connectivity/from_structured_grid.hpp" -#include "maia/__old/utils/cgns_tree_examples/simple_meshes.hpp" - -using std::array; -using namespace maia; -using namespace maia::six_hexa_mesh; - -TEST_CASE("generate for maia::six_hexa_mesh") { - using MI = std_e::multi_index; - MI vertex_dims = {4,3,2}; - - // Checks - CHECK( generate_hex_8(vertex_dims,MI{0,0,0}) == cell_vtx[0] ); - CHECK( generate_hex_8(vertex_dims,MI{1,0,0}) == cell_vtx[1] ); - CHECK( generate_hex_8(vertex_dims,MI{2,0,0}) == cell_vtx[2] ); - CHECK( generate_hex_8(vertex_dims,MI{0,1,0}) == cell_vtx[3] ); - CHECK( generate_hex_8(vertex_dims,MI{1,1,0}) == cell_vtx[4] ); - CHECK( generate_hex_8(vertex_dims,MI{2,1,0}) == cell_vtx[5] ); - - CHECK( generate_quad_4_normal_to<0>(vertex_dims,MI{0,0,0}) == i_face_vtx[0] ); - CHECK( generate_quad_4_normal_to<0>(vertex_dims,MI{0,1,0}) == i_face_vtx[1] ); - CHECK( generate_quad_4_normal_to<0>(vertex_dims,MI{1,0,0}) == i_face_vtx[2] ); - CHECK( generate_quad_4_normal_to<0>(vertex_dims,MI{1,1,0}) == i_face_vtx[3] ); - CHECK( generate_quad_4_normal_to<0>(vertex_dims,MI{2,0,0}) == i_face_vtx[4] ); - CHECK( generate_quad_4_normal_to<0>(vertex_dims,MI{2,1,0}) == i_face_vtx[5] ); - CHECK( generate_quad_4_normal_to<0>(vertex_dims,MI{3,0,0}) == i_face_vtx[6] ); - CHECK( generate_quad_4_normal_to<0>(vertex_dims,MI{3,1,0}) == i_face_vtx[7] ); - - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{0,0,0}) == j_face_vtx[0] ); - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{1,0,0}) == j_face_vtx[1] ); - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{2,0,0}) == j_face_vtx[2] ); - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{0,1,0}) == j_face_vtx[3] ); - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{1,1,0}) == j_face_vtx[4] ); - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{2,1,0}) == j_face_vtx[5] ); - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{0,2,0}) == j_face_vtx[6] ); - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{1,2,0}) == j_face_vtx[7] ); - CHECK( generate_quad_4_normal_to<1>(vertex_dims,MI{2,2,0}) == j_face_vtx[8] ); - - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{0,0,0}) == k_face_vtx[0] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{1,0,0}) == k_face_vtx[1] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{2,0,0}) == k_face_vtx[2] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{0,1,0}) == k_face_vtx[3] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{1,1,0}) == k_face_vtx[4] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{2,1,0}) == k_face_vtx[5] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{0,0,1}) == k_face_vtx[6] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{1,0,1}) == k_face_vtx[7] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{2,0,1}) == k_face_vtx[8] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{0,1,1}) == k_face_vtx[9] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{1,1,1}) == k_face_vtx[10] ); - CHECK( generate_quad_4_normal_to<2>(vertex_dims,MI{2,1,1}) == k_face_vtx[11] ); -}; diff --git a/maia/algo/dist/elements_to_ngons/elements_to_ngons.cpp b/maia/algo/dist/elements_to_ngons/elements_to_ngons.cpp deleted file mode 100644 index 636c5a29..00000000 --- a/maia/algo/dist/elements_to_ngons/elements_to_ngons.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/elements_to_ngons/elements_to_ngons.hpp" - -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.hpp" - -#include "std_e/parallel/struct/distributed_array.hpp" -#include "std_e/algorithm/distribution/weighted.hpp" -#include "maia/pytree/maia/element_sections.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "std_e/algorithm/algorithm.hpp" -#include "std_e/parallel/algorithm/concatenate/concatenate.hpp" -#include "std_e/future/ranges.hpp" -#include "maia/utils/logging/log.hpp" -#include "maia/pytree/maia/maia_cgns.hpp" -#include "cpp_cgns/sids.hpp" -#include "maia/utils/parallel/distribution.hpp" - - -using namespace cgns; - - -namespace maia { - -auto -element_conversion_traits(ElementType_t from_type, ElementType_t to_type) -> std::pair { - if (to_type==cgns::NGON_n ) return {"ElementConnectivity",number_of_vertices(from_type)}; - if (to_type==cgns::NFACE_n) return {"CellFace" ,number_of_faces (from_type)}; - STD_E_ASSERT(0); throw std::logic_error("expects NGON_n or NFACE_n"); -} - -template auto -concatenate_into_poly_section(const cgns::tree_range& section_nodes, ElementType_t poly_section_type, MPI_Comm comm) { - int n_section = section_nodes.size(); - std::vector n_elts(n_section); - std::vector w_elts(n_section); - std::vector> connec_distri_by_section(n_section); - std::vector> connec_by_section(n_section); - for (int i=0; i(elt_node,connec_name); - - auto elt_distri = ElementDistribution(elt_node); - connec_distri_by_section[i] = partial_to_full_distribution(elt_distri,comm); - std_e::scale(connec_distri_by_section[i],w_elts[i]); - } - - // 0. create a balanced distribution - auto [distri_cat,connec_distri_cat] = std_e::balanced_distribution(std_e::n_rank(comm),n_elts,w_elts); - - // 1. concatenate connectivities - std::vector connec_array = std_e::concatenate_arrays(connec_by_section,connec_distri_by_section,connec_distri_cat,comm); - - // 2. compute connectivity indices (ElementStartOffset) - int rk = std_e::rank(comm); - auto ns = std_e::n_elements_in_interval(distri_cat.interval(rk),n_elts); - std::vector elt_start_offset(distri_cat.length(rk) + 1); - elt_start_offset[0] = connec_distri_cat[rk]; - - auto f = elt_start_offset.begin()+1; - for (int i=0; i auto -ngon_from_faces(const tree_range& face_sections, MPI_Comm comm) -> tree { - // 0. concatenate connectivities - auto [distri_cat,ngon_section_node] = concatenate_into_poly_section(face_sections,cgns::NGON_n,comm); - - // 1. concatenate parents - /// 1.0. query tree - int n_section = face_sections.size(); - std::vector> l_pe_by_section(n_section); - std::vector> r_pe_by_section(n_section); - std::vector> l_pp_by_section(n_section); - std::vector> r_pp_by_section(n_section); - std::vector> distri_by_section(n_section); - for (int i=0; i(elt_node); - auto pp = ParentElementsPosition(elt_node); - l_pe_by_section[i] = column(pe,0); - r_pe_by_section[i] = column(pe,1); - l_pp_by_section[i] = column(pp,0); - r_pp_by_section[i] = column(pp,1); - - auto elt_distri = ElementDistribution(elt_node); - distri_by_section[i] = partial_to_full_distribution(elt_distri,comm); - } - /// 1.2. concatenate - std::vector l_pe_cat = std_e::concatenate_arrays(l_pe_by_section,distri_by_section,distri_cat,comm); - std::vector r_pe_cat = std_e::concatenate_arrays(r_pe_by_section,distri_by_section,distri_cat,comm); - std::vector l_pp_cat = std_e::concatenate_arrays(l_pp_by_section,distri_by_section,distri_cat,comm); - std::vector r_pp_cat = std_e::concatenate_arrays(r_pp_by_section,distri_by_section,distri_cat,comm); - - I8 parent_cat_sz = l_pe_cat.size(); - /// 1.3. parent elements - cgns::md_array pe_array(parent_cat_sz,2); - auto [_0,mid_pe_array] = std::ranges::copy(l_pe_cat,begin(pe_array)); - std::ranges::copy(r_pe_cat,mid_pe_array); - tree pe_node = cgns::new_DataArray("ParentElements",std::move(pe_array)); - emplace_child(ngon_section_node,std::move(pe_node)); - - /// 1.4. parent positions - cgns::md_array pp_array(parent_cat_sz,2); - auto [_1,mid_pp_array] = std::ranges::copy(l_pp_cat,begin(pp_array)); - std::ranges::copy(r_pp_cat,mid_pp_array); - tree pp_node = cgns::new_DataArray("ParentElementsPosition",std::move(pp_array)); - emplace_child(ngon_section_node,std::move(pp_node)); - - #if defined REAL_GCC && __GNUC__ >= 11 - return ngon_section_node; - #else // It seems like GCC 10 uses the copy-ctor instead of mandatory RVO - return std::move(ngon_section_node); - #endif -} - -template auto -nface_from_cells(const tree_range& cell_sections, MPI_Comm comm) -> tree { - auto [_,nfaces_section_node] = concatenate_into_poly_section(cell_sections,cgns::NFACE_n,comm); - #if defined REAL_GCC && __GNUC__ >= 11 - return nfaces_section_node; - #else // It seems like GCC 10 uses the copy-ctor instead of mandatory RVO - return std::move(nfaces_section_node); - #endif -} - -template auto -_turn_into_ngon_nface(tree& z, MPI_Comm comm) -> void { - auto elt_sections = element_sections(z); - auto elt_2D_3D_names = elt_sections - | std::views::filter([](const tree& x){ return is_section_of_dimension(x,2) || is_section_of_dimension(x,3); }) - | std::views::transform([](const tree& x){ return name(x); }) - | std_e::to_vector(); - emplace_child(z, ngon_from_faces (surface_element_sections(z),comm)); - emplace_child(z, nface_from_cells(volume_element_sections (z),comm)); - cgns::rm_children_by_names(z,elt_2D_3D_names); -} - -auto -turn_into_ngon_nface(tree& z, MPI_Comm comm) -> void { - if (value(z).data_type()=="I4") return _turn_into_ngon_nface(z,comm); - if (value(z).data_type()=="I8") return _turn_into_ngon_nface(z,comm); - throw cgns_exception("Zone "+name(z)+" has a value of data type "+value(z).data_type()+" but it should be I4 or I8"); -} - -auto -elements_to_ngons(tree& z, MPI_Comm comm) -> void { - STD_E_ASSERT(is_maia_compliant_zone(z)); - auto _ = maia_perf_log_lvl_0("elements_to_ngons"); - - generate_interior_faces_and_parents(z,comm); - turn_into_ngon_nface(z,comm); -} - - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/elements_to_ngons.hpp b/maia/algo/dist/elements_to_ngons/elements_to_ngons.hpp deleted file mode 100644 index 8f1453a6..00000000 --- a/maia/algo/dist/elements_to_ngons/elements_to_ngons.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include - - -namespace maia { - - -auto elements_to_ngons(cgns::tree& z, MPI_Comm comm) -> void; - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/add_cell_face_connectivities.cpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/add_cell_face_connectivities.cpp deleted file mode 100644 index 4b95be20..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/add_cell_face_connectivities.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/add_cell_face_connectivities.hpp" - -#include "cpp_cgns/sids/creation.hpp" -#include "std_e/parallel/struct/distributed_array.hpp" -#include "std_e/algorithm/partition/copy.hpp" -#include "std_e/future/ranges.hpp" -#include "std_e/parallel/mpi/collective/scan.hpp" -#include "std_e/parallel/mpi/collective/reduce.hpp" -#include "std_e/algorithm/algorithm.hpp" -#include "maia/pytree/maia/element_sections.hpp" -#include "maia/utils/parallel/distribution.hpp" -#include "std_e/data_structure/multi_range/multi_range.hpp" - - -using namespace cgns; - - -namespace maia { - - -template -struct cell_face_info { - std::vector cell_indices; - std::vector cell_face_ids; -}; -enum class parent_side { - left, - right, -}; - -template auto -add_face_info(cell_face_info& x, parent_side side, const auto& face_info, ElementType_t cell_type, I cell_first_id) -> void { - auto [cell_id,face_position_in_cell,face_id] = face_info; - I cell_index = cell_id-cell_first_id; - auto n_face_of_cell = number_of_faces(cell_type); - I first_face_in_vol_index = cell_index*n_face_of_cell; - I face_in_vol_index = first_face_in_vol_index + (face_position_in_cell-1); // -1 because CGNS starts at 1 - x.cell_indices .push_back(face_in_vol_index); - if (side == parent_side::left ) x.cell_face_ids.push_back( face_id); - if (side == parent_side::right) x.cell_face_ids.push_back(-face_id); -} - -template auto -add_cell_face_infos( - std::vector>& cell_face_info_by_section, - parent_side side, - const auto& faces_info, - const auto& cell_section_intervals, const auto& cell_section_types -) - -> void -{ - // Precondition - for (auto [cell_id,_0,_1] : faces_info) { - STD_E_ASSERT(cell_section_intervals[0]<=cell_id && cell_id auto -fill_cell_face_info( - std::vector>& cell_face_info_by_section, - const tree_range& cell_sections, - const in_ext_faces& faces, - I first_in_id, - MPI_Comm comm -) -{ - auto cell_section_intervals = element_sections_interval_vector(cell_sections); - auto cell_section_types = cell_sections - | std::views::transform([](const tree& e){ return element_type(e); }) - | std_e::to_vector(); - - const auto& ext_face_ids = faces.ext.bnd_face_parent_elements; - const auto& ext_pe = faces.ext.cell_parent_elements; - const auto& ext_pp = faces.ext.cell_parent_positions; - - I n_face = faces.in.size(); - I n_face_acc = std_e::ex_scan(n_face,MPI_SUM,0,comm); - const auto& in_face_ids = std_e::iota_vector(n_face,first_in_id+n_face_acc); - const auto& in_l_pe = faces.in.l_parent_elements; - const auto& in_l_pp = faces.in.l_parent_positions; - const auto& in_r_pe = faces.in.r_parent_elements; - const auto& in_r_pp = faces.in.r_parent_positions; - - auto ext_faces_info = std_e::view_as_multi_range( ext_pe, ext_pp, ext_face_ids); - auto in_l_faces_info = std_e::view_as_multi_range(in_l_pe, in_l_pp, in_face_ids); - auto in_r_faces_info = std_e::view_as_multi_range(in_r_pe, in_r_pp, in_face_ids); - - add_cell_face_infos(cell_face_info_by_section, parent_side::left , ext_faces_info, cell_section_intervals, cell_section_types); - add_cell_face_infos(cell_face_info_by_section, parent_side::left , in_l_faces_info, cell_section_intervals, cell_section_types); - add_cell_face_infos(cell_face_info_by_section, parent_side::right, in_r_faces_info, cell_section_intervals, cell_section_types); -} - - -template auto -compute_cell_face(tree_range& cell_sections, const in_ext_faces_by_section& unique_faces_sections, I first_interior_face_id, MPI_Comm comm) { - int n_cell_section = cell_sections.size(); - std::vector> cell_face_info_by_section(n_cell_section); - - I current_last_face_id = first_interior_face_id; - for(const auto& fs : unique_faces_sections) { - fill_cell_face_info(cell_face_info_by_section,cell_sections,fs,current_last_face_id, comm); - - current_last_face_id += std_e::all_reduce(fs.in.size(),MPI_SUM,comm); - } - - return cell_face_info_by_section; -} - - -template auto -append_cell_face_info(tree& vol_section, std::vector&& cell_indices, std::vector&& cell_face_ids, MPI_Comm comm) { - auto partial_dist = ElementDistribution(vol_section); - - auto dist_cell_face = maia::distribution_from_partial(partial_dist,comm); - std_e::scale(dist_cell_face,number_of_faces(element_type(vol_section))); - - std_e::dist_array cell_face(dist_cell_face,comm); - auto sp = create_exchange_protocol(dist_cell_face,std::move(cell_indices)); - std_e::scatter(sp,cell_face_ids,cell_face); - - std::vector connec_array(begin(cell_face.local()),end(cell_face.local())); - emplace_child(vol_section,cgns::new_DataArray("CellFace",std::move(connec_array))); -} - - -template auto -add_cell_face_connectivities(tree_range& cell_sections, const in_ext_faces_by_section& unique_faces_sections, I first_interior_face_id, MPI_Comm comm) -> void { - int n_cell_section = cell_sections.size(); - - // for each cell section, find its face ids - auto cell_face_info_by_section = compute_cell_face(cell_sections,unique_faces_sections,first_interior_face_id,comm); - - // add CellFace node to cell sections - for (int i=0; i& unique_faces_sections, I4 first_interior_face_id, MPI_Comm comm) -> void; -template auto add_cell_face_connectivities(cgns::tree_range& cell_sections, const in_ext_faces_by_section& unique_faces_sections, I8 first_interior_face_id, MPI_Comm comm) -> void; - - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/add_cell_face_connectivities.hpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/add_cell_face_connectivities.hpp deleted file mode 100644 index fff0f0f9..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/add_cell_face_connectivities.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/in_ext_faces_by_section.hpp" -#include "mpi.h" - - -namespace maia { - - -template auto -add_cell_face_connectivities(cgns::tree_range& cell_sections, const in_ext_faces_by_section& unique_faces_sections, I first_interior_face_id, MPI_Comm comm) -> void; - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/append_interior_faces_sections.cpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/append_interior_faces_sections.cpp deleted file mode 100644 index 7e07ed69..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/append_interior_faces_sections.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/append_interior_faces_sections.hpp" -#include "cpp_cgns/sids/creation.hpp" - -#include "std_e/parallel/mpi/collective/scan.hpp" -#include "std_e/parallel/mpi/collective/reduce.hpp" -#include "maia/pytree/maia/element_sections.hpp" - - -using namespace cgns; - - -namespace maia { - - -template auto -create_interior_faces_section(in_faces_with_parents&& fps, I section_first_id, ElementType_t face_type, MPI_Comm comm) { - I n_face = fps.size(); - - I n_face_tot = std_e::all_reduce(n_face,MPI_SUM,comm); - I n_face_acc = std_e::ex_scan(n_face,MPI_SUM,0,comm); - - // connectivity - I section_last_id = section_first_id+n_face_tot-1; - tree elt_section_node = new_Elements(to_string(face_type)+"_interior",face_type,std::move(fps.connec),section_first_id,section_last_id); - - // parent element - md_array pe_array(n_face,2); - auto [_0,mid_pe_array] = std::ranges::copy(fps.l_parent_elements,begin(pe_array)); - std::ranges::copy(fps.r_parent_elements,mid_pe_array); - - tree pe_node = cgns::new_DataArray("ParentElements",std::move(pe_array)); - emplace_child(elt_section_node,std::move(pe_node)); - - // parent position - md_array pp_array(n_face,2); - auto [_1,mid_pp_array] = std::ranges::copy(fps.l_parent_positions,begin(pp_array)); - std::ranges::copy(fps.r_parent_positions,mid_pp_array); - - tree parent_position_elt_node = cgns::new_DataArray("ParentElementsPosition",std::move(pp_array)); - emplace_child(elt_section_node,std::move(parent_position_elt_node)); - - // distribution - std::vector elt_dist(3); - elt_dist[0] = n_face_acc; - elt_dist[1] = n_face_acc + n_face; - elt_dist[2] = n_face_tot; - auto dist_node = new_Distribution("Element",std::move(elt_dist)); - emplace_child(elt_section_node,std::move(dist_node)); - - return std::make_pair(n_face_tot,std::move(elt_section_node)); -} - - -template auto -append_interior_faces_sections(cgns::tree& z, in_ext_faces_by_section&& faces_sections, I first_interior_face_id, MPI_Comm comm) -> I { - I current_section_id = first_interior_face_id; - for(auto& fs : faces_sections) { - auto [n_in_faces,in_section_node] = create_interior_faces_section(std::move(fs.in),current_section_id,fs.face_type,comm); - current_section_id += n_in_faces; - emplace_child(z,std::move(in_section_node)); - } - return current_section_id; -} - - -// Explicit instanciations of functions defined in this .cpp file -template auto append_interior_faces_sections(cgns::tree& z, in_ext_faces_by_section&& faces_sections, I4 first_interior_face_id, MPI_Comm comm) -> I4; -template auto append_interior_faces_sections(cgns::tree& z, in_ext_faces_by_section&& faces_sections, I8 first_interior_face_id, MPI_Comm comm) -> I8; - - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/append_interior_faces_sections.hpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/append_interior_faces_sections.hpp deleted file mode 100644 index 7cf2acb4..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/append_interior_faces_sections.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/in_ext_faces_by_section.hpp" -#include "mpi.h" - - -using namespace cgns; - - -namespace maia { - - -template auto -append_interior_faces_sections(cgns::tree& z, in_ext_faces_by_section&& faces_sections, I first_interior_face_id, MPI_Comm comm) -> I; - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.cpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.cpp deleted file mode 100644 index eae67b58..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.hpp" - -#include "maia/algo/dist/elements_to_ngons/connectivity/element_faces.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/faces_and_parents_by_section.hpp" - - -using namespace cgns; - - -namespace maia { - - -template auto -number_of_faces(const tree_range& elt_sections) -> std::array { - std::array n_faces_by_type; - std::ranges::fill(n_faces_by_type,0); - - for (const tree& e : elt_sections) { - auto elt_type = element_type(e); - I n_elt = distribution_local_size(ElementDistribution(e)); - for (int i=0; i auto -gen_faces( - const tree& elt_node, - auto& tri_it , auto& quad_it, - I*& tri_pe_it, I*& quad_pe_it, - I*& tri_pp_it, I*& quad_pp_it -) -{ - constexpr int n_vtx = number_of_vertices(elt_type); - auto elt_connec = ElementConnectivity(elt_node); - auto connec_range = std_e::view_as_block_range(elt_connec); - - I elt_start = ElementRange(elt_node)[0]; - I index_dist_start = ElementDistribution(elt_node)[0]; - I elt_id = elt_start + index_dist_start; - - for (const auto& elt : connec_range) { - generate_faces(elt,tri_it,quad_it); - generate_parent_positions(tri_pp_it,quad_pp_it); - tri_pe_it = std::fill_n( tri_pe_it,cgns::number_of_faces(elt_type,TRI_3 ),elt_id); - quad_pe_it = std::fill_n(quad_pe_it,cgns::number_of_faces(elt_type,QUAD_4),elt_id); - ++elt_id; - } -} - - -template auto -generate_element_faces_and_parents(const tree_range& elt_sections) -> faces_and_parents_by_section { - //auto _ = std_e::stdout_time_logger("generate_element_faces_and_parents"); - - auto n_faces_by_type = number_of_faces(elt_sections); - connectivities_with_parents tris (TRI_3 ,cgns::at_face_type(n_faces_by_type,TRI_3 )); - connectivities_with_parents quads(QUAD_4,cgns::at_face_type(n_faces_by_type,QUAD_4)); - - auto tri_it = connectivities<3>(tris ).begin(); - auto quad_it = connectivities<4>(quads).begin(); - auto tri_pe_it = parent_elements (tris ).begin(); - auto quad_pe_it = parent_elements (quads).begin(); - auto tri_pp_it = parent_positions (tris ).begin(); - auto quad_pp_it = parent_positions (quads).begin(); - for (const auto& elt_section : elt_sections) { - auto elt_type = element_type(elt_section); - switch(elt_type){ - case TRI_3: { - gen_faces(elt_section,tri_it,quad_it,tri_pe_it,quad_pe_it,tri_pp_it,quad_pp_it); - break; - } - case QUAD_4: { - gen_faces(elt_section,tri_it,quad_it,tri_pe_it,quad_pe_it,tri_pp_it,quad_pp_it); - break; - } - case TETRA_4: { - gen_faces(elt_section,tri_it,quad_it,tri_pe_it,quad_pe_it,tri_pp_it,quad_pp_it); - break; - } - case PENTA_6: { - gen_faces(elt_section,tri_it,quad_it,tri_pe_it,quad_pe_it,tri_pp_it,quad_pp_it); - break; - } - case PYRA_5: { - gen_faces(elt_section,tri_it,quad_it,tri_pe_it,quad_pe_it,tri_pp_it,quad_pp_it); - break; - } - case HEXA_8: { - gen_faces(elt_section,tri_it,quad_it,tri_pe_it,quad_pe_it,tri_pp_it,quad_pp_it); - break; - } - case BAR_2: { - break; // an edge has no face! - } - default: { - throw cgns_exception(std::string("Function \"")+__func__+"\": not implemented for element type \""+to_string(elt_type)+"\""); - } - } - } - return {std::move(tris),std::move(quads)}; -} - - -// Explicit instanciations of functions defined in this .cpp file -template auto generate_element_faces_and_parents(const tree_range& elt_sections) -> faces_and_parents_by_section; -template auto generate_element_faces_and_parents(const tree_range& elt_sections) -> faces_and_parents_by_section; - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.hpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.hpp deleted file mode 100644 index 855f1f2a..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "cpp_cgns/sids/elements_utils.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/faces_and_parents_by_section.hpp" - - -namespace maia { - - -template auto -generate_element_faces_and_parents(const cgns::tree_range& elt_sections) -> faces_and_parents_by_section; - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/merge_unique_faces.cpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/merge_unique_faces.cpp deleted file mode 100644 index 46d32712..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/merge_unique_faces.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/merge_unique_faces.hpp" - - -#include "maia/utils/logging/log.hpp" -#include "maia/utils/parallel/distribution.hpp" -#include "std_e/algorithm/rotate/rotate.hpp" -#include "std_e/parallel/mpi/extra_types.hpp" -#include "std_e/algorithm/partition_sort.hpp" -#include "std_e/algorithm/sorting_networks.hpp" -#include "std_e/parallel/algorithm/sort_by_rank.hpp" -#include "std_e/data_structure/multi_range/multi_range.hpp" -#include "std_e/future/sort/sort_ranges.hpp" - -#if defined REAL_GCC && __GNUC__ < 11 - using std_e::get; // found by ADL with GCC 11 -#endif - -using namespace cgns; - - -namespace maia { - - -auto -same_face_but_flipped(const auto& f0, const auto& f1) { - STD_E_ASSERT(f0.size()==f1.size()); - int n = f0.size(); - STD_E_ASSERT(n>0); - STD_E_ASSERT(f0[0]==f1[0]); // Precondition here - for (int i=1; i auto -merge_unique(const auto& cs, const auto& pe, const auto& pp, I first_3d_elt_id) -> in_ext_faces { - // NOTE: - // pe means parent elements - // pp means parent positions - - //auto _ = std_e::stdout_time_logger(" merge_unique"); - - // 0. prepare - /// 0.0. sizes - constexpr int n_vtx_of_face_type = number_of_vertices(face_type); - auto n_faces = cs.size(); - - /// 0.1. interior faces - std::vector connec_int; connec_int.reserve(cs.total_size()/2); - std::vector l_pe_int; l_pe_int .reserve(pe.size()/2); - std::vector r_pe_int; r_pe_int .reserve(pe.size()/2); - std::vector l_pp_int; l_pp_int .reserve(pp.size()/2); - std::vector r_pp_int; r_pp_int .reserve(pp.size()/2); - auto cs_int = std_e::view_as_block_range(connec_int); - - /// 0.2. exterior faces - std::vector face_pe_ext; - std::vector cell_pe_ext; - std::vector cell_pp_ext; - - // 1. keep one face and gather parents two by two - for (I4 i=0; i= first_3d_elt_id); - face_pe_ext.push_back(pe[i]); - cell_pe_ext.push_back(pe[i+1]); - cell_pp_ext.push_back(pp[i+1]); - } else { - STD_E_ASSERT(pe[i+1] < first_3d_elt_id); - face_pe_ext.push_back(pe[i+1]); - cell_pe_ext.push_back(pe[i]); - cell_pp_ext.push_back(pp[i]); - } - } else { - STD_E_ASSERT(same_face_but_flipped(cs[i],cs[i+1])); - if (pe[i] >= first_3d_elt_id && pe[i+1] >= first_3d_elt_id) { // two 3d pe - if (pe[i] < pe[i+1]) { - cs_int.push_back(cs[i]); - l_pe_int.push_back(pe[i]); - r_pe_int.push_back(pe[i+1]); - l_pp_int.push_back(pp[i]); - r_pp_int.push_back(pp[i+1]); - } else { - cs_int.push_back(cs[i+1]); - l_pe_int.push_back(pe[i+1]); - r_pe_int.push_back(pe[i]); - l_pp_int.push_back(pp[i+1]); - r_pp_int.push_back(pp[i]); - } - } else if (pe[i] < first_3d_elt_id) { // first parent is 2d and the normal is inward (wrong convention) - STD_E_ASSERT(pe[i+1] >= first_3d_elt_id); - face_pe_ext.push_back(pe[i]); - cell_pe_ext.push_back(pe[i+1]); - cell_pp_ext.push_back(pp[i+1]); - } else { // second parent is 2d and the normal is inward (wrong convention) - STD_E_ASSERT(pe[i+1] < first_3d_elt_id); - face_pe_ext.push_back(pe[i+1]); - cell_pe_ext.push_back(pe[i]); - cell_pp_ext.push_back(pp[i]); - } - } - } - - // 2. gather results - return { - face_type, - ext_faces_with_parents{face_pe_ext,cell_pe_ext,cell_pp_ext}, - in_faces_with_parents{connec_int,l_pe_int,r_pe_int,l_pp_int,r_pp_int} - }; -} - -template auto -merge_faces_of_type(connectivities_with_parents& cps, I first_3d_elt_id, MPI_Comm comm) -> in_ext_faces { - //auto _ = maia_perf_log_lvl_2("merge_unique_faces"); - - // 0. prepare - constexpr int n_vtx_of_face_type = number_of_vertices(face_type); - auto cs = connectivities(cps); - auto pe = parent_elements(cps); - auto pp = parent_positions(cps); - - // 1. for each face, reorder its vertices so that the smallest is first - for (auto c : cs) { - std_e::rotate_min_first(c); - } - - // 2. pre-sort the arrays - // Note: elements are compared by their first vertex only - // this is good enought for load balancing - auto mr = view_as_multi_range(cs,pe,pp); - auto proj_on_first_vertex = [](const auto& x){ return get<0>(x)[0]; }; // returns cs[0], i.e. the first vertex - auto rank_indices = std_e::sort_by_rank(mr,comm,proj_on_first_vertex); - - - // 3. exchange - auto [res_cs,_0] = std_e::all_to_all(cs,rank_indices,comm); - auto [res_pe,_1] = std_e::all_to_all(pe,rank_indices,comm); - auto [res_pp,_2] = std_e::all_to_all(pp,rank_indices,comm); - - STD_E_ASSERT(res_cs.size()%2==0); // each face should be there twice - // (either two 3d parents if interior, - // or one 2d and one 3d parent if exterior) - - // 4. continue sorting locally - /// 4.0. do a lexico ordering of each face, in an auxilliary array - auto res_cs_ordered = deep_copy(res_cs); - for (auto c : res_cs_ordered) { - // since the first vtx is already the smallest, no need to include it in the sort - std_e::sorting_network::sort(c.begin()+1); - } - /// 4.1. do the sort based on this lexico ordering - auto res_mr = view_as_multi_range(res_cs_ordered,res_cs,res_pe,res_pp); - auto proj_on_ordered_cs = [](const auto& x){ return get<0>(x); }; // returns res_cs_ordered - std_e::ranges::sort(res_mr,{},proj_on_ordered_cs); - - return merge_unique(res_cs,res_pe,res_pp,first_3d_elt_id); -} - -template auto -merge_unique_faces(faces_and_parents_by_section& faces_and_parents_sections, I first_3d_elt_id, MPI_Comm comm) - -> in_ext_faces_by_section -{ - in_ext_faces_by_section ie_faces; - for (auto& fps : faces_and_parents_sections) { - auto elt_type = element_type(fps); - if (elt_type==TRI_3) { - ie_faces.emplace_back( merge_faces_of_type(fps,first_3d_elt_id,comm) ); - } else if (elt_type==QUAD_4) { - ie_faces.emplace_back( merge_faces_of_type(fps,first_3d_elt_id,comm) ); - } else { - throw cgns_exception(std::string("Function \"")+__func__+"\": not implemented for element type \""+to_string(elt_type)+"\""); - } - } - return ie_faces; -} - -// Explicit instanciations of functions defined in this .cpp file -template auto merge_unique_faces(faces_and_parents_by_section& faces_and_parents_sections, I4 first_3d_elt_id, MPI_Comm comm) - -> in_ext_faces_by_section; - -template auto merge_unique_faces(faces_and_parents_by_section& faces_and_parents_sections, I8 first_3d_elt_id, MPI_Comm comm) - -> in_ext_faces_by_section; - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/merge_unique_faces.hpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/merge_unique_faces.hpp deleted file mode 100644 index 90f36fe7..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/merge_unique_faces.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - - -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/faces_and_parents_by_section.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/in_ext_faces_by_section.hpp" -#include "mpi.h" - - -namespace maia { - - -template auto -merge_unique_faces(faces_and_parents_by_section& faces_and_parents_sections, I first_3d_elt_id, MPI_Comm comm) - -> in_ext_faces_by_section; - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/scatter_parents_to_boundary_sections.hpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/scatter_parents_to_boundary_sections.hpp deleted file mode 100644 index 6e823b8e..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/scatter_parents_to_boundary_sections.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - - -#include -#include "cpp_cgns/cgns.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/in_ext_faces_by_section.hpp" -#include "maia/utils/parallel/distribution.hpp" -#include "std_e/algorithm/algorithm.hpp" -#include "std_e/parallel/struct/distributed_array.hpp" -#include "cpp_cgns/sids/creation.hpp" - - -namespace maia { - - -template auto -scatter_parents_to_boundary(cgns::tree& bnd_section, const ext_faces_with_parents& ext_faces, MPI_Comm comm) { - // 1. Boundary face indices in the array are just boundary face parent element ids, but starting at 0 - auto first_face_id = ElementRange(bnd_section)[0]; - std::vector face_indices = ext_faces.bnd_face_parent_elements; - std_e::offset(face_indices,-first_face_id); - - // 2. Send parent cell info to the boundary faces - // 2.0. Protocol creation - auto partial_distri = ElementDistribution(bnd_section); - auto distri = maia::distribution_from_partial(partial_distri,comm); - auto sp = create_exchange_protocol(distri,std::move(face_indices)); - - // 2.1. parent_elements - std_e::dist_array pe(distri,comm); - std_e::scatter(sp,ext_faces.cell_parent_elements,pe); - - I n_face_res = pe.local().size(); - cgns::md_array pe_array(n_face_res,2); - std::ranges::copy(pe.local(),begin(pe_array)); // only half is assign, the other is 0 - - cgns::tree pe_node = cgns::new_DataArray("ParentElements",std::move(pe_array)); - emplace_child(bnd_section,std::move(pe_node)); - - // 2.2. parent_positions - std_e::dist_array pp(distri,comm); - std_e::scatter(sp,ext_faces.cell_parent_positions,pp); - - cgns::md_array pp_array(n_face_res,2); - std::ranges::copy(pp.local(),begin(pp_array)); // only half is assign, the other is 0 - - cgns::tree pp_node = cgns::new_DataArray("ParentElementsPosition",std::move(pp_array)); - emplace_child(bnd_section,std::move(pp_node)); -} - - -template auto -scatter_parents_to_boundary_sections(Tree_range& bnd_sections, const in_ext_faces_by_section& unique_faces_sections, MPI_Comm comm) -> void { - for(const auto& fs : unique_faces_sections) { - auto bnd_section = std::ranges::find_if(bnd_sections,[&fs](const cgns::tree& t){ return element_type(t)==fs.face_type ; }); - if (bnd_section != end(bnd_sections)) { - scatter_parents_to_boundary(*bnd_section,fs.ext,comm); - } else { - STD_E_ASSERT(fs.ext.size()==0); - } - } -} - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/test/element_faces_and_parents.test.cpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/test/element_faces_and_parents.test.cpp deleted file mode 100644 index 10a28156..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/test/element_faces_and_parents.test.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest.hpp" - -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.hpp" -#include "maia/utils/yaml/parse_yaml_cgns.hpp" - -using namespace cgns; -using namespace maia; -using std::vector; - -TEST_CASE("generate_element_faces_and_parents") { - std::string yaml_tree = - "Quads Elements_t I4 [7,0]:\n" - " ElementRange IndexRange_t I4 [400,400]:\n" - " ElementConnectivity DataArray_t:\n" - " I4 : [ 40, 41, 42, 43 ]\n" - " :CGNS#Distribution UserDefinedData_t:\n" - " Element DataArray_t I4 [0, 1, 1]:\n" - "Tris Elements_t I4 [5,0]:\n" - " ElementRange IndexRange_t I4 [300,301]:\n" - " ElementConnectivity DataArray_t:\n" - " I4 : [ 30, 31, 32,\n" - " 37, 38, 39 ]\n" - " :CGNS#Distribution UserDefinedData_t:\n" - " Element DataArray_t I4 [0, 2, 2]:\n" - "Hexas Elements_t I4 [17,0]:\n" - " ElementRange IndexRange_t I4 [800,810]:\n" - " ElementConnectivity DataArray_t:\n" - " I4 : [ 80, 81, 82, 83, 84, 85, 86, 87 ]\n" - " :CGNS#Distribution UserDefinedData_t:\n" - " Element DataArray_t I4 [2, 3, 10]:\n"; - vector elt_sections = maia::to_nodes(yaml_tree); - cgns::tree_range elt_sections_rng(begin(elt_sections),end(elt_sections)); - - faces_and_parents_by_section fps = generate_element_faces_and_parents(elt_sections_rng); - auto& tris = fps[0]; - auto& quads = fps[1]; - CHECK( element_type(tris) == TRI_3 ); - CHECK( size(tris) == 2 ); // two tri faces - CHECK( connectivities<3>(tris)[0] == vector{30,31,32} ); - CHECK( connectivities<3>(tris)[1] == vector{37,38,39} ); - CHECK( parent_elements(tris) == vector{300,301} ); - CHECK( parent_positions(tris) == vector{1,1} ); - - CHECK( element_type(quads) == QUAD_4 ); - CHECK( size(quads) == 1+6 ); // one quad face + 6 faces of 1 cube - CHECK( connectivities<4>(quads)[0] == vector{40,41,42,43} ); - CHECK( connectivities<4>(quads)[1] == vector{80,83,82,81} ); - // ... - CHECK( parent_elements(quads) == vector{400,802,802,802,802,802,802} ); // 800+2 because the distribution of node "Hexas" begins at 2 - CHECK( parent_positions(quads) == vector{1,1,2,3,4,5,6} ); -} -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.cpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.cpp deleted file mode 100644 index 5af390aa..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.hpp" - -#include "maia/pytree/maia/element_sections.hpp" -#include "maia/pytree/maia/maia_cgns.hpp" -#include "maia/utils/logging/log.hpp" - -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/element_faces_and_parents.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/merge_unique_faces.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/scatter_parents_to_boundary_sections.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/add_cell_face_connectivities.hpp" -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/algo/append_interior_faces_sections.hpp" -#include "maia/algo/common/shift.hpp" - - -using namespace cgns; - - -namespace maia { - - -template auto -_generate_interior_faces_and_parents(cgns::tree& z, MPI_Comm comm) -> void { - // 0. Base queries - auto elt_sections = element_sections(z); - auto bnd_face_sections = surface_element_sections(z); - auto cell_sections = volume_element_sections(z); - - // 1. Generate faces - auto faces_and_parents_sections = generate_element_faces_and_parents(elt_sections); - - // 2. Make them unique - I first_3d_elt_id = elements_interval(cell_sections).first(); - auto unique_faces_sections = merge_unique_faces(faces_and_parents_sections,first_3d_elt_id,comm); - - // 3. Send parent info back to original exterior faces - scatter_parents_to_boundary_sections(bnd_face_sections,unique_faces_sections,comm); - - // 4. Start of interior faces just after exterior faces - I old_first_cell_id = elements_interval(cell_sections).first(); - I first_interior_face_id = old_first_cell_id; - - // 5. Compute cell_face - add_cell_face_connectivities(cell_sections,unique_faces_sections,first_interior_face_id,comm); - - // 6. Create new interior faces sections - I next_available_elt_id = append_interior_faces_sections(z,std::move(unique_faces_sections),first_interior_face_id,comm); - - // 7. Shift cell ids to leave room for interior faces - I cell_offset = next_available_elt_id - old_first_cell_id; - shift_cell_ids(z,cell_offset); -}; - -auto -generate_interior_faces_and_parents(cgns::tree& z, MPI_Comm comm) -> void { - STD_E_ASSERT(is_maia_compliant_zone(z)); - if (value(z).data_type()=="I4") return _generate_interior_faces_and_parents(z,comm); - if (value(z).data_type()=="I8") return _generate_interior_faces_and_parents(z,comm); - throw cgns_exception("Zone "+name(z)+" has a value of data type "+value(z).data_type()+" but it should be I4 or I8"); -} - - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.hpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.hpp deleted file mode 100644 index 2a7c703a..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include - - -namespace maia { - - -auto generate_interior_faces_and_parents(cgns::tree& z, MPI_Comm comm) -> void; - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/faces_and_parents_by_section.hpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/faces_and_parents_by_section.hpp deleted file mode 100644 index ed216159..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/faces_and_parents_by_section.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - - -#include -#include "cpp_cgns/sids/elements_utils/faces.hpp" -#include "cpp_cgns/base/data_type.hpp" -#include "std_e/data_structure/block_range/block_range.hpp" - - -namespace maia { - - -template -class connectivities_with_parents { - // Class invariant: connectivities().size()/number_of_vertices(elt_type) == parents().size() == parent_positions().size() - private: - cgns::ElementType_t elt_type; - std::vector connec; - std::vector pe; - std::vector pp; - public: - // ctors - connectivities_with_parents() = default; - - connectivities_with_parents(cgns::ElementType_t elt_type, I n_connec) - : elt_type(elt_type) - , connec(n_connec*number_of_vertices(elt_type)) - , pe(n_connec) - , pp(n_connec) - {} - - // Access - // Done through friend functions because one of them is template, - // and calling a template member function has an ugly syntax. - // The other functions are also friend for consistency - friend auto - size(const connectivities_with_parents& x) -> size_t { - return x.pe.size(); - } - friend auto - element_type(const connectivities_with_parents& x) -> cgns::ElementType_t { - return x.elt_type; - } - template friend auto - connectivities(connectivities_with_parents& x); - friend auto - parent_elements(connectivities_with_parents& x) -> std_e::span { - return std_e::make_span(x.pe); - } - friend auto - parent_positions(connectivities_with_parents& x) -> std_e::span { - return std_e::make_span(x.pp); - } -}; - -template auto -connectivities(connectivities_with_parents& x) { - STD_E_ASSERT(n_vtx == number_of_vertices(x.elt_type)); - return std_e::view_as_block_range(x.connec); -} - - -template -using faces_and_parents_by_section = std::vector>; - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/in_ext_faces_by_section.hpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/in_ext_faces_by_section.hpp deleted file mode 100644 index 64576565..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/struct/in_ext_faces_by_section.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - - -#include - - -namespace maia { - - -// Note: we don't bother encapsulating data members -// because this type is only used as a temporary computation result - - -template -struct ext_faces_with_parents { - // Class invariant: - // all sizes are equal - std::vector bnd_face_parent_elements; - std::vector cell_parent_elements; - std::vector cell_parent_positions; - auto size() const -> I { return bnd_face_parent_elements.size(); } -}; -template -struct in_faces_with_parents { - // Class invariant: - // all sizes are equal - std::vector connec; - std::vector l_parent_elements; - std::vector r_parent_elements; - std::vector l_parent_positions; - std::vector r_parent_positions; - auto size() const -> I { return l_parent_elements.size(); } -}; - - -template -struct in_ext_faces { - cgns::ElementType_t face_type; - ext_faces_with_parents ext; - in_faces_with_parents in; -}; - - -template -using in_ext_faces_by_section = std::vector>; - - -} // maia diff --git a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/test/interior_faces_and_parents.test.cpp b/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/test/interior_faces_and_parents.test.cpp deleted file mode 100644 index e4413db7..00000000 --- a/maia/algo/dist/elements_to_ngons/interior_faces_and_parents/test/interior_faces_and_parents.test.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest_pybind_mpi.hpp" - -#include "maia/io/file.hpp" -#include "maia/utils/mesh_dir.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "maia/pytree/maia/element_sections.hpp" - -#include "maia/algo/dist/elements_to_ngons/interior_faces_and_parents/interior_faces_and_parents.hpp" -#include "std_e/parallel/mpi/base.hpp" -#include "std_e/multi_array/utils.hpp" - - -using namespace cgns; - - -PYBIND_MPI_TEST_CASE("generate_interior_faces_and_parents - seq",1) { - std::string file_name = maia::mesh_dir+"hex_2_prism_2.yaml"; - tree t = maia::file_to_dist_tree(file_name,MPI_COMM_SELF); - tree& z = cgns::get_node_by_matching(t,"Base/Zone"); - - auto old_hexa_first_id = cgns::get_node_value_by_matching(z,"Hexas/ElementRange")[0]; - - maia::generate_interior_faces_and_parents(z,test_comm); - - auto new_hexa_first_id = cgns::get_node_value_by_matching(z,"Hexas/ElementRange")[0]; - auto n_in_faces = new_hexa_first_id-old_hexa_first_id; - CHECK( n_in_faces == 4 ); - - // tri ext - auto pe_tri_ext = cgns::get_node_value_by_matching(z,"Tris/ParentElements"); - auto pp_tri_ext = cgns::get_node_value_by_matching(z,"Tris/ParentElementsPosition"); - CHECK( pe_tri_ext.extent() == std_e::multi_index{2,2} ); - CHECK( pe_tri_ext == cgns::md_array{{21,0},{22,0}} ); - CHECK( pp_tri_ext == cgns::md_array{{ 4,0},{ 5,0}} ); - - // tri in - auto elt_type_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior")[0]; - auto range_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior/ElementRange"); - auto connec_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior/ElementConnectivity"); - auto pe_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior/ParentElements"); - auto pp_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior/ParentElementsPosition"); - - CHECK( elt_type_tri_in == (I4)cgns::TRI_3 ); - CHECK( range_tri_in == std::vector{15,15} ); - CHECK( connec_tri_in == std::vector{7,8,10} ); - CHECK( pe_tri_in.extent() == std_e::multi_index{1,2} ); - CHECK( pe_tri_in == cgns::md_array{{21,22}} ); - CHECK( pp_tri_in == cgns::md_array{{ 5, 4}} ); - - // quad ext - auto pe_quad_ext = cgns::get_node_value_by_matching(z,"Quads/ParentElements"); - auto pp_quad_ext = cgns::get_node_value_by_matching(z,"Quads/ParentElementsPosition"); - CHECK( pe_quad_ext.extent() == std_e::multi_index{12,2} ); - CHECK( pe_quad_ext == cgns::md_array{{19,0},{20,0},{21,0},{22,0},{19,0},{21,0},{20,0},{22,0},{19,0},{20,0},{19,0},{20,0}} ); - CHECK( pp_quad_ext == cgns::md_array{{ 5,0},{ 5,0},{ 2,0},{ 2,0},{ 2,0},{ 1,0},{ 2,0},{ 1,0},{ 4,0},{ 4,0},{ 1,0},{ 6,0}} ); - - // quad in - auto elt_type_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior")[0]; - auto range_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior/ElementRange"); - auto connec_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior/ElementConnectivity"); - auto pe_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior/ParentElements"); - auto pp_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior/ParentElementsPosition"); - - CHECK( elt_type_quad_in == (I4)cgns::QUAD_4 ); - CHECK( range_quad_in == std::vector{16,18} ); - CHECK( connec_quad_in == std::vector{2,5,10,7, 6,7,10,9, 7,10,15,12} ); - CHECK( pe_quad_in.extent() == std_e::multi_index{3,2} ); - CHECK( pe_quad_in == cgns::md_array{{19,21},{19,20},{20,22}} ); - CHECK( pp_quad_in == cgns::md_array{{ 3, 3},{ 6, 1},{ 3, 3}} ); - - // hex - auto hex_cell_face = cgns::get_node_value_by_matching(z,"Hexas/CellFace"); - CHECK( hex_cell_face == std::vector{11,5,16,9,1,17, -17,7,18,10,2,12} ); - // prisms - auto prism_cell_face = cgns::get_node_value_by_matching(z,"Prisms/CellFace"); - CHECK( prism_cell_face == std::vector{6,3,-16,13,15, 8,4,-18,-15,14} ); -} - - -PYBIND_MPI_TEST_CASE("generate_interior_faces_and_parents",2) { - std::string file_name = maia::mesh_dir+"hex_2_prism_2.yaml"; - tree t = maia::file_to_dist_tree(file_name,test_comm); - tree& z = cgns::get_node_by_matching(t,"Base/Zone"); - - maia::generate_interior_faces_and_parents(z,test_comm); - // tri ext - auto pe_tri_ext = cgns::get_node_value_by_matching(z,"Tris/ParentElements"); - auto pp_tri_ext = cgns::get_node_value_by_matching(z,"Tris/ParentElementsPosition"); - CHECK( pe_tri_ext.extent() == std_e::multi_index{1,2} ); - MPI_CHECK(0, pe_tri_ext == cgns::md_array{{21,0}} ); - MPI_CHECK(1, pe_tri_ext == cgns::md_array{{22,0}} ); - MPI_CHECK(0, pp_tri_ext == cgns::md_array{{ 4,0} } ); - MPI_CHECK(1, pp_tri_ext == cgns::md_array{ { 5,0}} ); - - // tri in - auto elt_type_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior")[0]; - auto range_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior/ElementRange"); - auto connec_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior/ElementConnectivity"); - auto pe_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior/ParentElements"); - auto pp_tri_in = cgns::get_node_value_by_matching(z,"TRI_3_interior/ParentElementsPosition"); - - CHECK( elt_type_tri_in == (I4)cgns::TRI_3 ); - CHECK( range_tri_in == std::vector{15,15} ); - MPI_CHECK(0, connec_tri_in == std::vector{} ); - MPI_CHECK(1, connec_tri_in == std::vector{7,8,10} ); - MPI_CHECK(0, pe_tri_in.extent() == std_e::multi_index{0,2} ); - MPI_CHECK(1, pe_tri_in.extent() == std_e::multi_index{1,2} ); - MPI_CHECK(0, pe_tri_in == cgns::md_array(0,2) ); - MPI_CHECK(1, pe_tri_in == cgns::md_array{{21,22}} ); - MPI_CHECK(0, pp_tri_in == cgns::md_array(0,2) ); - MPI_CHECK(1, pp_tri_in == cgns::md_array{{ 5, 4}} ); - - // quad ext - auto pe_quad_ext = cgns::get_node_value_by_matching(z,"Quads/ParentElements"); - auto pp_quad_ext = cgns::get_node_value_by_matching(z,"Quads/ParentElementsPosition"); - CHECK( pe_quad_ext.extent() == std_e::multi_index{6,2} ); - MPI_CHECK(0, pe_quad_ext == cgns::md_array{{19,0},{20,0},{21,0},{22,0},{19,0},{21,0} } ); - MPI_CHECK(1, pe_quad_ext == cgns::md_array{ {20,0},{22,0},{19,0},{20,0},{19,0},{20,0}} ); - MPI_CHECK(0, pp_quad_ext == cgns::md_array{{ 5,0},{ 5,0},{ 2,0},{ 2,0},{ 2,0},{ 1,0} } ); - MPI_CHECK(1, pp_quad_ext == cgns::md_array{ { 2,0},{ 1,0},{ 4,0},{ 4,0},{ 1,0},{ 6,0}} ); - - // quad in - auto elt_type_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior")[0]; - auto range_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior/ElementRange"); - auto connec_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior/ElementConnectivity"); - auto pe_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior/ParentElements"); - auto pp_quad_in = cgns::get_node_value_by_matching(z,"QUAD_4_interior/ParentElementsPosition"); - - CHECK( elt_type_quad_in == (I4)cgns::QUAD_4 ); - CHECK( range_quad_in == std::vector{16,18} ); - MPI_CHECK(0, connec_quad_in == std::vector{2,5,10,7} ); - MPI_CHECK(1, connec_quad_in == std::vector{6,7,10,9, 7,10,15,12} ); - MPI_CHECK(0, pe_quad_in.extent() == std_e::multi_index{1,2} ); - MPI_CHECK(1, pe_quad_in.extent() == std_e::multi_index{2,2} ); - MPI_CHECK(0, pe_quad_in == cgns::md_array{{19,21}} ); - MPI_CHECK(1, pe_quad_in == cgns::md_array{{19,20},{20,22}} ); - MPI_CHECK(0, pp_quad_in == cgns::md_array{{ 3, 3}} ); - MPI_CHECK(1, pp_quad_in == cgns::md_array{{ 6, 1},{ 3, 3}} ); - - // hex - auto hex_cell_face = cgns::get_node_value_by_matching(z,"Hexas/CellFace"); - MPI_CHECK(0, hex_cell_face == std::vector{11,5,16,9,1,17 } ); - MPI_CHECK(1, hex_cell_face == std::vector{ -17,7,18,10,2,12} ); - // prisms - auto prism_cell_face = cgns::get_node_value_by_matching(z,"Prisms/CellFace"); - MPI_CHECK(0, prism_cell_face == std::vector{6,3,-16,13,15 } ); - MPI_CHECK(1, prism_cell_face == std::vector{ 8,4,-18,-15,14} ); -} -#endif // C++>17 diff --git a/maia/algo/dist/elements_to_ngons/test/elements_to_ngons.test.cpp b/maia/algo/dist/elements_to_ngons/test/elements_to_ngons.test.cpp deleted file mode 100644 index 805600f4..00000000 --- a/maia/algo/dist/elements_to_ngons/test/elements_to_ngons.test.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest_pybind_mpi.hpp" - -#include "maia/io/file.hpp" -#include "maia/utils/mesh_dir.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "cpp_cgns/sids/elements_utils.hpp" - -#include "maia/algo/dist/elements_to_ngons/elements_to_ngons.hpp" -#include "std_e/parallel/mpi/base.hpp" - - -using namespace cgns; - - -PYBIND_MPI_TEST_CASE("elements_to_ngons",2) { - std::string file_name = maia::mesh_dir+"hex_2_prism_2.yaml"; - tree t = maia::file_to_dist_tree(file_name,test_comm); - tree& z = cgns::get_node_by_matching(t,"Base/Zone"); - - maia::elements_to_ngons(z,test_comm); - - auto elt_type = cgns::get_node_value_by_matching(z,"NGON_n")[0]; - auto range = cgns::get_node_value_by_matching(z,"NGON_n/ElementRange"); - auto eso = cgns::get_node_value_by_matching(z,"NGON_n/ElementStartOffset"); - auto connec = cgns::get_node_value_by_matching(z,"NGON_n/ElementConnectivity"); - auto pe = cgns::get_node_value_by_matching(z,"NGON_n/ParentElements"); - auto pp = cgns::get_node_value_by_matching(z,"NGON_n/ParentElementsPosition"); - CHECK( elt_type == (I4)cgns::NGON_n); - CHECK( range == std::vector{1,18} ); - MPI_CHECK(0, eso == std::vector{0,4,8,12,16,20,24,28,32} ); - MPI_CHECK(0, connec == std::vector{1,6,9,4, 6,11,14,9, 3,5,10,8, 8,10,15,13, 1,2,7,6, 2,3,8,7, 6,7,12,11 ,7,8,13,12} ); - MPI_CHECK(1, eso == std::vector{32,36,40,44,48,51,54,57,61,65,69} ); - MPI_CHECK(1, connec == std::vector{4,9,10,5, 9,14,15,10, 1,4,5,2, 11,12,15,14, 2,5,3, 12,13,15, 7,8,10, 2,5,10,7, 6,7,10,9, 7,10,15,12} ); - MPI_CHECK(0, pe.extent() == std_e::multi_index{8,2} ); - MPI_CHECK(1, pe.extent() == std_e::multi_index{10,2} ); - MPI_CHECK(0, pe == cgns::md_array{{19,0},{20,0},{21,0},{22,0},{19,0},{21,0},{20, 0},{22, 0}} ); - MPI_CHECK(1, pe == cgns::md_array{{19,0},{20,0},{19,0},{20,0},{21,0},{22,0},{21,22},{19,21},{19,20},{20,22}} ); - MPI_CHECK(0, pp == cgns::md_array{{5,0},{5,0},{2,0},{2,0},{2,0},{1,0},{2,0},{1,0}} ); - MPI_CHECK(1, pp == cgns::md_array{{4,0},{4,0},{1,0},{6,0},{4,0},{5,0},{5,4},{3,3},{6,1},{3,3}} ); -} -#endif // C++>17 diff --git a/maia/algo/dist/extract_surf_dmesh.py b/maia/algo/dist/extract_surf_dmesh.py deleted file mode 100644 index a35e990c..00000000 --- a/maia/algo/dist/extract_surf_dmesh.py +++ /dev/null @@ -1,118 +0,0 @@ -import numpy as np -from Pypdm.Pypdm import dconnectivity_to_extract_dconnectivity, part_dcoordinates_to_pcoordinates - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.utils import np_utils, par_utils, layouts -from maia.utils import logging as mlog -from maia.transfer.dist_to_part.index_exchange import collect_distributed_pl - - -def _extract_faces(dist_zone, face_list, comm): - """ - CGNS wrapping for Pypdm.dconnectivity_to_extract_dconnectivity : extract the faces - specified in face_list from the distributed zone - """ - - # > Try to hook NGon - ngon_node = PT.Zone.NGonNode(dist_zone) - dface_vtx = PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1] - ngon_eso = PT.get_child_from_name(ngon_node, 'ElementStartOffset' )[1] - - distrib_face = PT.get_value(MT.getDistribution(ngon_node, 'Element')) - distrib_face_vtx = PT.get_value(MT.getDistribution(ngon_node, 'ElementConnectivity')) - - dn_face = distrib_face[1] - distrib_face[0] - np_face_distrib = par_utils.partial_to_full_distribution(distrib_face, comm) - - dface_vtx_idx = np.add(ngon_eso, -distrib_face_vtx[0], dtype=np.int32) #Local index is int32bits - - return dconnectivity_to_extract_dconnectivity(comm, face_list, np_face_distrib, dface_vtx_idx, dface_vtx) - -def _extract_surf_zone(dist_zone, face_list, comm): - """ - Extract the specified faces and create a CGNS 2d Zone containing only these faces. - Extracted zone connectivity is described by a NGon Element node - """ - n_rank = comm.Get_size() - i_rank = comm.Get_rank() - - # > Extract faces - ex_face_distri, ex_vtx_distri, ex_face_vtx_idx, ex_face_vtx, ex_face_parent_gnum, ex_vtx_parent_gnum, \ - ex_face_old_to_new = _extract_faces(dist_zone, face_list, comm) - - # > Transfert extracted vertex coordinates - distrib_vtx = PT.get_value(MT.getDistribution(dist_zone, 'Vertex')) - dn_vtx = distrib_vtx [1] - distrib_vtx [0] - if dn_vtx > 0: - cx, cy, cz = PT.Zone.coordinates(dist_zone) - dvtx_coord = np_utils.interweave_arrays([cx,cy,cz]) - - - ini_vtx_distri = par_utils.partial_to_full_distribution(distrib_vtx, comm) - ex_vtx_coords = part_dcoordinates_to_pcoordinates(comm, ini_vtx_distri, dvtx_coord, [ex_vtx_parent_gnum])[0] - - # Create extracted zone - dist_extract_zone = PT.new_Zone(name = PT.get_name(dist_zone) + '_surf', - size = [[ex_vtx_distri[n_rank],ex_face_distri[n_rank],0]], - type = 'Unstructured') - - ex_fvtx_distri = par_utils.gather_and_shift(ex_face_vtx_idx[-1], comm, pdm_gnum_dtype) - - # > Grid coordinates - cx, cy, cz = layouts.interlaced_to_tuple_coords(ex_vtx_coords) - coords = {'CoordinateX': cx, 'CoordinateY': cy, 'CoordinateZ': cz} - grid_coord = PT.new_GridCoordinates(fields=coords, parent=dist_extract_zone) - - eso = ex_fvtx_distri[i_rank] + ex_face_vtx_idx.astype(pdm_gnum_dtype) - extract_ngon_n = PT.new_NGonElements(erange = [1, ex_face_distri[n_rank]], parent=dist_extract_zone, - eso=eso, ec=ex_face_vtx) - - np_distrib_vtx = ex_vtx_distri [[i_rank, i_rank+1, n_rank]] - np_distrib_face = ex_face_distri[[i_rank, i_rank+1, n_rank]] - np_distrib_facevtx = ex_fvtx_distri[[i_rank, i_rank+1, n_rank]] - - MT.newDistribution({'Cell' : np_distrib_face, 'Vertex' : np_distrib_vtx}, dist_extract_zone) - MT.newDistribution({'Element' : np_distrib_face, 'ElementConnectivity' : np_distrib_facevtx}, extract_ngon_n) - - return dist_extract_zone - -def extract_surf_zone_from_queries(dist_zone, queries, comm): - """ - Create a zone containing a surfacic mesh, extracted from all the faces found under the - nodes matched by one of the queries - """ - - all_point_list = collect_distributed_pl(dist_zone, queries, filter_loc='FaceCenter') - _, dface_list = np_utils.concatenate_point_list(all_point_list, pdm_gnum_dtype) - - return _extract_surf_zone(dist_zone, dface_list, comm) - -def extract_surf_tree_from_queries(dist_tree, queries, comm): - """ - For each zone in the input dist_tree, find the faces under the nodes matched by one - of the queries and extract the surfacic zone. Return a surfacic dist_tree including - all of this zones - """ - - surf_tree = PT.new_CGNSTree() - for base, zone in PT.iter_children_from_predicates(dist_tree, ['CGNSBase_t', 'Zone_t'], ancestors=True): - surf_base = PT.update_child(surf_tree, PT.get_name(base), 'CGNSBase_t', [2,2]) - - if PT.Zone.Type(zone) == 'Unstructured': - surf_zone = extract_surf_zone_from_queries(zone, queries, comm) - PT.add_child(surf_base, surf_zone) - else: - mlog.warning(f"skip structured zone {PT.get_name(zone)} in extract_surf_tree") - - return surf_tree - -def extract_surf_tree_from_bc(dist_tree, comm): - """ - Shortcut for extract_surf_tree_from_queries specialized for BC_t nodes - """ - queries = [[lambda n: PT.get_label(n) == 'ZoneBC_t', lambda n: PT.get_label(n) == 'BC_t']] - return extract_surf_tree_from_queries(dist_tree, queries, comm) - diff --git a/maia/algo/dist/fsdm_distribution/__init__.py b/maia/algo/dist/fsdm_distribution/__init__.py deleted file mode 100644 index 3ffa186d..00000000 --- a/maia/algo/dist/fsdm_distribution/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from cmaia import dist_algo as cdist_algo -from maia.algo.apply_function_to_nodes import apply_to_bases,apply_to_zones - -from maia.utils import require_cpp20 - -@require_cpp20 -def add_fsdm_distribution(t, comm): - apply_to_bases(cdist_algo.add_fsdm_distribution, t, comm) - diff --git a/maia/algo/dist/fsdm_distribution/fsdm_distribution.cpp b/maia/algo/dist/fsdm_distribution/fsdm_distribution.cpp deleted file mode 100644 index cecd38d1..00000000 --- a/maia/algo/dist/fsdm_distribution/fsdm_distribution.cpp +++ /dev/null @@ -1,294 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/fsdm_distribution/fsdm_distribution.hpp" -#include "cpp_cgns/sids/Hierarchical_Structures.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "maia/utils/parallel/distribution.hpp" -#include "maia/pytree/maia/element_sections.hpp" -#include "pdm_multi_block_to_part.h" -#include "std_e/algorithm/algorithm.hpp" -#include "maia/utils/parallel/exchange/multi_block_to_part.hpp" -#include - -using namespace cgns; - -namespace maia { - -// TODO I4 -> I -auto add_fsdm_distribution(tree& b, MPI_Comm comm) -> void { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - auto zs = get_children_by_label(b,"Zone_t"); - if (zs.size()!=1) { - throw cgns_exception("add_fsdm_distribution (as FSDM) expects only one zone per process"); - } - tree& z = zs[0]; - - I4 n_vtx = VertexSize_U(z); - I4 n_vtx_owned = n_vtx = get_node_value_by_matching(z,":CGNS#LocalNumbering/VertexSizeOwned")[0];; - auto vtx_distri = distribution_from_dsizes(n_vtx_owned, comm); - auto partial_vtx_distri = full_to_partial_distribution(vtx_distri,comm); - std::vector vtx_distri_mem(begin(partial_vtx_distri),end(partial_vtx_distri)); - tree vtx_dist = new_DataArray("Vertex",std::move(vtx_distri_mem)); - auto dist_node = new_UserDefinedData(":CGNS#Distribution"); - emplace_child(dist_node,std::move(vtx_dist)); - emplace_child(z,std::move(dist_node)); - - auto elt_sections = get_children_by_label(z,"Elements_t"); - for (tree& elt_section : elt_sections) { - auto elt_range = ElementRange(elt_section); - I4 n_owned_elt = elt_range[1] - elt_range[0] + 1; - - auto elt_distri = distribution_from_dsizes(n_owned_elt, comm); - auto partial_elt_distri = full_to_partial_distribution(elt_distri,comm); - tree elt_dist = new_DataArray("Element",std::move(partial_elt_distri)); - - auto dist_node = new_UserDefinedData(":CGNS#Distribution"); - emplace_child(dist_node,std::move(elt_dist)); - emplace_child(elt_section,std::move(dist_node)); - } -} - -template auto -elt_interval_range(const Tree_range& sorted_elt_sections) { - int n_elt = sorted_elt_sections.size(); - std::vector interval_rng(n_elt+1); - - for (int i=0; i(sorted_elt_sections[i])[0]; - } - - interval_rng[n_elt] = ElementRange(sorted_elt_sections.back())[1]+1; // +1 because CGNS intervals are closed, we want open - - return interval_rng; -} - -template auto -elt_distributions(const Tree_range& sorted_elt_sections, MPI_Comm comm) { - int n_elt = sorted_elt_sections.size(); - std::vector> dists(n_elt); - for (int i=0; i(elt); - auto dist = distribution_from_partial(partial_dist,comm); - dists[i] = distribution_vector(dist.n_interval()); // TODO make resize accessible - std::copy(begin(dist),end(dist),begin(dists[i])); - } - return dists; -} - - -auto -distribute_bc_ids_to_match_face_dist(tree& b, MPI_Comm comm) -> void { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - for (tree& z : get_children_by_label(b,"Zone_t")) { - auto elt_sections = element_sections_ordered_by_range(z); - auto elt_intervals = elt_interval_range(elt_sections); - auto elt_dists = elt_distributions(elt_sections,comm); - - for (tree& bc : cgns::get_nodes_by_matching(z,"ZoneBC/BC_t")) { - auto pl = cgns::PointList(bc); - auto fields = std::vector>{}; // TODO extract these fields (if they exist) - auto [new_pl,_] = redistribute_to_match_face_dist(elt_dists,elt_intervals,pl,fields,comm); - - rm_child_by_name(bc,"PointList"); - - cgns::emplace_child(bc,new_PointList("PointList",std::move(new_pl))); - } - // TODO update BC distribution - } -} - - -auto -distribute_vol_fields_to_match_global_element_range(cgns::tree& b, MPI_Comm comm) -> void { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - int i_rank = std_e::rank(comm); - int n_rank = std_e::n_rank(comm); - - for (tree& z : get_children_by_label(b,"Zone_t")) { - int n_cell = cgns::CellSize_U(z); - - auto elt_sections = element_sections_ordered_by_range(z); - auto elt_intervals = elt_interval_range(elt_sections); - auto elt_dists = elt_distributions(elt_sections,comm); - - int n_section = elt_sections.size(); - - auto section_is_2d = [](const tree& x){ return element_dimension(element_type(x))==2; }; - STD_E_ASSERT(std::is_partitioned(begin(elt_sections),end(elt_sections),section_is_2d)); - auto first_section_3d = std::partition_point(begin(elt_sections),end(elt_sections),section_is_2d); - int n_2d_section = first_section_3d - begin(elt_sections); - auto elt_3d_sections = std_e::make_span(elt_sections.data()+n_2d_section,elt_sections.data()+n_section); - - int n_3d_section = n_section-n_2d_section; - - // multi-block to part - std::vector> distribs(n_3d_section); - std::vector d_elt_szs(n_3d_section); - for (int i=0; i(section_node); - d_elt_szs[i] = section_connec_partial_distri[1]-section_connec_partial_distri[0]; - distribs[i] = distribution_from_partial(section_connec_partial_distri,comm); - } - - const int n_block = n_3d_section; - - std::vector merged_distri(n_rank+1); - std_e::uniform_distribution(begin(merged_distri),end(merged_distri),0,(PDM_g_num_t)n_cell); // TODO uniform_distribution with differing args - - int n_elts = merged_distri[i_rank+1]-merged_distri[i_rank]; - std::vector ln_to_gn(n_elts); - std::iota(begin(ln_to_gn),end(ln_to_gn),merged_distri[i_rank]+1); - - pdm::multi_block_to_part_protocol mbtp(distribs,ln_to_gn,comm); - - tree_range flow_sol_nodes = cgns::get_children_by_labels(z,{"FlowSolution_t","DiscreteData_t"}); - for (tree& flow_sol_node : flow_sol_nodes) { - tree_range sol_nodes = cgns::get_children_by_label(flow_sol_node,"DataArray_t"); - for (tree& sol_node : sol_nodes) { - auto sol = get_value(sol_node); - std::vector> d_arrays(n_block); - int offset = 0; - for (int i=0; i new_sol = mbtp.exchange(d_arrays); - - value(sol_node) = cgns::node_value(std::move(new_sol)); - } - } - - tree& z_dist_node = cgns::get_child_by_name(z,":CGNS#Distribution"); - std::vector cell_partial_dist = {merged_distri[i_rank],merged_distri[i_rank+1],merged_distri.back()}; - emplace_child(z_dist_node,new_DataArray("Cell",std::move(cell_partial_dist))); - } -} - -auto -distribute_fields_to_match_global_element_range(cgns::tree& b, MPI_Comm comm) -> void { - STD_E_ASSERT(label(b)=="CGNSBase_t"); - int i_rank = std_e::rank(comm); - int n_rank = std_e::n_rank(comm); - - for (tree& z : get_children_by_label(b,"Zone_t")) { - // 0. preparation - int n_cell = cgns::CellSize_U(z); - - auto elt_sections = element_sections_ordered_by_range(z); - auto elt_intervals = elt_interval_range(elt_sections); - auto elt_dists = elt_distributions(elt_sections,comm); - - int n_section = elt_sections.size(); - - auto section_is_2d = [](const tree& x){ return element_dimension(element_type(x))==2; }; - STD_E_ASSERT(std::is_partitioned(begin(elt_sections),end(elt_sections),section_is_2d)); - auto first_section_3d = std::partition_point(begin(elt_sections),end(elt_sections),section_is_2d); - - int n_2d_section = first_section_3d - begin(elt_sections); - int n_3d_section = n_section-n_2d_section; - - auto elt_2d_sections = std_e::make_span(elt_sections.data() ,elt_sections.data()+n_2d_section); - auto elt_3d_sections = std_e::make_span(elt_sections.data()+n_2d_section,elt_sections.data()+n_section); - - // 1. multi-block to part for volume fields - { - std::vector> distribs(n_3d_section); - std::vector d_elt_szs(n_3d_section); - for (int i=0; i(section_node); - d_elt_szs[i] = section_connec_partial_distri[1]-section_connec_partial_distri[0]; - distribs[i] = distribution_from_partial(section_connec_partial_distri,comm); - } - - const int n_block = n_3d_section; - - std::vector merged_distri(n_rank+1); - std_e::uniform_distribution(begin(merged_distri),end(merged_distri),0,(PDM_g_num_t)n_cell); // TODO uniform_distribution with differing args - - int n_elts = merged_distri[i_rank+1]-merged_distri[i_rank]; - std::vector ln_to_gn(n_elts); - std::iota(begin(ln_to_gn),end(ln_to_gn),merged_distri[i_rank]+1); - - pdm::multi_block_to_part_protocol mbtp(distribs,ln_to_gn,comm); - - tree_range flow_sol_nodes = cgns::get_children_by_labels(z,{"FlowSolution_t","DiscreteData_t"}); - for (tree& flow_sol_node : flow_sol_nodes) { - tree_range sol_nodes = cgns::get_children_by_label(flow_sol_node,"DataArray_t"); - for (tree& sol_node : sol_nodes) { - auto sol = get_value(sol_node); - std::vector> d_arrays(n_block); - int offset = 0; - for (int i=0; i new_sol = mbtp.exchange(d_arrays); - - value(sol_node) = cgns::node_value(std::move(new_sol)); - } - } - - tree& z_dist_node = cgns::get_child_by_name(z,":CGNS#Distribution"); - std::vector cell_partial_dist = {merged_distri[i_rank],merged_distri[i_rank+1],merged_distri.back()}; - emplace_child(z_dist_node,new_DataArray("Cell",std::move(cell_partial_dist))); - } - - // 2. multi-block to part for boundary fields // TODO factor with 1. - { - std::vector> distribs(n_2d_section); - std::vector d_elt_szs(n_2d_section); - for (int i=0; i(section_node); - d_elt_szs[i] = section_connec_partial_distri[1]-section_connec_partial_distri[0]; - distribs[i] = distribution_from_partial(section_connec_partial_distri,comm); - } - - const int n_block = n_2d_section; - - PDM_g_num_t n_faces = 0; - if (elt_2d_sections.size() > 0) { - // since 2d sections are ordered, the total number of 2d elt is the ending of the last range - n_faces = ElementRange(elt_2d_sections.back())[1]; - } - std::vector merged_distri(n_rank+1); - std_e::uniform_distribution(begin(merged_distri),end(merged_distri),0,n_faces); // TODO uniform_distribution with differing args - - int n_elts = merged_distri[i_rank+1]-merged_distri[i_rank]; - std::vector ln_to_gn(n_elts); - std::iota(begin(ln_to_gn),end(ln_to_gn),merged_distri[i_rank]+1); - - pdm::multi_block_to_part_protocol mbtp(distribs,ln_to_gn,comm); - - tree_range bnd_sol_nodes = cgns::get_children_by_label(z,"ZoneSubRegion_t"); - for (tree& bnd_sol_node : bnd_sol_nodes) { - tree_range sol_nodes = cgns::get_children_by_label(bnd_sol_node,"DataArray_t"); - for (tree& sol_node : sol_nodes) { - auto sol = get_value(sol_node); - std::vector> d_arrays(n_block); - int offset = 0; - for (int i=0; i new_sol = mbtp.exchange(d_arrays); - - value(sol_node) = cgns::node_value(std::move(new_sol)); - } - std::vector part_dist = {merged_distri[i_rank],merged_distri[i_rank+1],merged_distri.back()}; - emplace_child(bnd_sol_node,new_Distribution("Index",std::move(part_dist))); - } - } - } -} - - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/fsdm_distribution/fsdm_distribution.hpp b/maia/algo/dist/fsdm_distribution/fsdm_distribution.hpp deleted file mode 100644 index 5c8dc35f..00000000 --- a/maia/algo/dist/fsdm_distribution/fsdm_distribution.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "mpi.h" - - -#include "std_e/data_structure/jagged_range.hpp" -#include "std_e/future/contract.hpp" -#include "std_e/algorithm/partition_sort.hpp" -#include "std_e/parallel/all_to_all.hpp" - - -namespace maia { - -auto add_fsdm_distribution(cgns::tree& b, MPI_Comm comm) -> void; - -auto distribute_bc_ids_to_match_face_dist(cgns::tree& b, MPI_Comm comm) -> void; -auto distribute_vol_fields_to_match_global_element_range(cgns::tree& b, MPI_Comm comm) -> void; -auto distribute_fields_to_match_global_element_range(cgns::tree& b, MPI_Comm comm) -> void; - - -// distribute_bc_ids_to_match_face_dist impl { -// Note: see this as a temporary until a multi_array from a "rack" is implemented -template auto -_transposed(const Range_of_ranges& x) { - // Precond: - // all ranges have the same size - // there is at least one range - using range_type = typename Range_of_ranges::value_type; - using T = typename range_type::value_type; - int m = x.size(); - int n = x[0].size(); - std::vector> xt(n); - for (int j=0; j(m); - for (int i=0; i auto -repartition_by_distributions( - const Dist_range& elt_dists, - const Interval_sequence& elt_intervals, - Range& point_list, - Range_of_ranges& values -) -{ - using I = typename Range::value_type; - std::vector perm_indices(point_list.size()); - std::iota(begin(perm_indices),end(perm_indices),0); - - // Note: could be done in place, but needs more infrastructure - auto multi_dist = _transposed(elt_dists); - int n_dist = multi_dist.size()-1; // TODO length - auto multi_dist_intervals = std_e::make_span(multi_dist.data()+1,n_dist); - - auto comp = [&elt_intervals,&point_list](I perm_indices, const auto& dist_interval){ - I id = point_list[perm_indices]; - int idx = std_e::interval_index(id,elt_intervals); - I base_id = id - elt_intervals[idx]; - return base_id < dist_interval[idx]; - }; - - auto partition_indices = std_e::partition_sort_indices(perm_indices,multi_dist_intervals,comp); - - std_e::permute(begin(point_list),perm_indices); - for (auto& value : values) { - std_e::permute(begin(value),perm_indices); - } - - return partition_indices; -} - - -template auto -redistribute_to_match_face_dist( - const Dist_range& elt_dists, - const Interval_sequence& elt_intervals, - Range& point_list, - Range_of_ranges& values, - MPI_Comm comm -) -{ - using I = typename Range::value_type; - - auto partition_indices = repartition_by_distributions(elt_dists,elt_intervals,point_list,values); - - std_e::jagged_span pl(std_e::make_span(point_list),std_e::make_span(partition_indices)); - std::vector pl_new = std_e::all_to_all_v(pl,comm).retrieve_values(); - - int n_value = values.size(); - using value_range = typename Range_of_ranges::value_type; - using T = typename value_range::value_type; - std::vector> values_new(n_value); - for (int i=0; i val(std_e::make_span(values[i]),std_e::make_span(partition_indices)); - values_new[i] = std_e::all_to_all_v(val,comm).retrieve_values(); - } - - return std::make_pair(pl_new,values_new); -} -// distribute_bc_ids_to_match_face_dist impl } - -} // maia diff --git a/maia/algo/dist/fsdm_distribution/test/fsdm_distribution.test.cpp b/maia/algo/dist/fsdm_distribution/test/fsdm_distribution.test.cpp deleted file mode 100644 index 4f8df98d..00000000 --- a/maia/algo/dist/fsdm_distribution/test/fsdm_distribution.test.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest_mpi.hpp" - -#include "maia/algo/dist/fsdm_distribution/fsdm_distribution.hpp" - -using std::vector; -MPI_TEST_CASE("distribute_bc_ids_to_match_face_dist",2) { - vector tri_dist = {0,10,21}; - vector quad_dist = {0,14,27}; - - vector element_intervals = {1,21,48}; // tris in [1,21), quads in [21,48) - - vector point_list; - vector> values; - if (test_rank==0) { - point_list = {40 , 1 , 2 ,13 ,41 }; - values = { { 0., 1., 2., 3., 4.}, - {10.,11.,12.,13.,14.} }; - } else { - STD_E_ASSERT(test_rank==1); - //point_list = {42 , 5 ,11 }; - point_list = {42 , 5 ,10, 11 }; // Note: 10 is on rank 0, because - values = { { 5., 6., 7., 8.}, - {15.,16.,17.,18.} }; - } - - SUBCASE("repartition_by_distributions") { - auto partition_indices = maia::repartition_by_distributions( - vector{tri_dist,quad_dist}, - element_intervals, - point_list, - values - ); - MPI_CHECK( 0 , point_list == vector{ 2 , 1 ,40 ,13 ,41 } ); - MPI_CHECK( 0 , values[0] == vector{ 2., 1., 0., 3., 4.} ); - MPI_CHECK( 0 , values[1] == vector{12.,11.,10.,13.,14.} ); - MPI_CHECK( 0 , partition_indices == std_e::interval_vector{0,2,5} ); - - MPI_CHECK( 1 , point_list == vector{10 , 5 ,42 ,11 } ); - MPI_CHECK( 1 , values[0] == vector{ 7., 6., 5., 8.} ); - MPI_CHECK( 1 , values[1] == vector{17.,16.,15.,18.} ); - MPI_CHECK( 1 , partition_indices == std_e::interval_vector{0,2,4} ); - } - - - SUBCASE("final") { - auto [pl_new,values_new] = maia::redistribute_to_match_face_dist( - vector{tri_dist,quad_dist}, - element_intervals, - point_list, - values, - test_comm - ); - - MPI_CHECK( 0 , pl_new == vector{ 2 , 1 ,10 , 5 } ); - MPI_CHECK( 0 , values_new[0] == vector{ 2., 1., 7., 6.} ); - MPI_CHECK( 0 , values_new[1] == vector{12.,11.,17.,16.} ); - MPI_CHECK( 1 , pl_new == vector{40 ,13 ,41 ,42 ,11 } ); - MPI_CHECK( 1 , values_new[0] == vector{ 0., 3., 4., 5., 8.} ); - MPI_CHECK( 1 , values_new[1] == vector{10.,13.,14.,15.,18.} ); - } -} -#endif // C++>17 diff --git a/maia/algo/dist/geometry.py b/maia/algo/dist/geometry.py deleted file mode 100644 index fb7e86d8..00000000 --- a/maia/algo/dist/geometry.py +++ /dev/null @@ -1,147 +0,0 @@ -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.algo import indexing -from maia.utils import np_utils, par_utils, as_pdm_gnum -from maia.transfer import protocols as EP -from .ngon_tools import PDM_dfacecell_to_dcellface - -import cmaia.part_algo as cpart_algo - -import Pypdm.Pypdm as PDM - -def _cell_vtx_connectivity(zone, comm): - """ - Return cell_vtx connectivity for an input NGON Zone - """ - if PT.Zone.has_ngon_elements(zone): - ngon_node = PT.Zone.NGonNode(zone) - face_vtx = PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1] - face_vtx_idx = PT.get_child_from_name(ngon_node, 'ElementStartOffset')[1] - face_distri = MT.get_distribution(ngon_node, 'Element')[1] - _face_distri = par_utils.partial_to_full_distribution(face_distri, comm) - _face_vtx_idx = np.empty(face_vtx_idx.size, np.int32) - np.subtract(face_vtx_idx, face_vtx_idx[0], out=_face_vtx_idx) - if PT.Zone.has_nface_elements(zone): - nface_node = PT.Zone.NFaceNode(zone) - cell_face = PT.get_child_from_name(nface_node, 'ElementConnectivity')[1] - cell_distri = MT.get_distribution(nface_node, 'Element')[1] - _cell_distri = par_utils.partial_to_full_distribution(cell_distri, comm) - cell_face_idx = PT.get_child_from_name(nface_node, 'ElementStartOffset')[1] - _cell_face_idx = np.empty(cell_face_idx.size, np.int32) - np.subtract(cell_face_idx, cell_face_idx[0], out=_cell_face_idx) - - else: - assert PT.Element.Range(ngon_node)[0] == 1 - local_pe = indexing.get_ngon_pe_local(ngon_node).reshape(-1, order='C') - cell_distri = MT.get_distribution(zone, 'Cell')[1] - _cell_distri = par_utils.partial_to_full_distribution(cell_distri, comm) - _cell_face_idx, cell_face = PDM_dfacecell_to_dcellface(comm, _face_distri, _cell_distri, local_pe) - _cell_face_idx = np_utils.safe_int_cast(_cell_face_idx, np.int32) - - cell_vtx_idx, cell_vtx = PDM.dconnectivity_combine(comm, - as_pdm_gnum(_cell_distri), - as_pdm_gnum(_face_distri), - _cell_face_idx, - as_pdm_gnum(cell_face), - _face_vtx_idx, - as_pdm_gnum(face_vtx), - False) - else: - raise NotImplementedError("Only NGON zones are managed") - - return cell_vtx_idx, cell_vtx - -def _mean_coords_from_connectivity(vtx_id_idx, cx_expd, cy_expd, cz_expd): - """ Coordinates should be repeted to match the size of vtx_id_idx """ - - vtx_id_n = np.diff(vtx_id_idx) - - mean_x = np.add.reduceat(cx_expd, vtx_id_idx[:-1]) / vtx_id_n - mean_y = np.add.reduceat(cy_expd, vtx_id_idx[:-1]) / vtx_id_n - mean_z = np.add.reduceat(cz_expd, vtx_id_idx[:-1]) / vtx_id_n - - return np_utils.interweave_arrays([mean_x, mean_y, mean_z]) - -@PT.check_is_label("Zone_t") -def compute_face_normal(zone, comm): - """Compute the face normal of a distributed zone. - - Input zone must have cartesian coordinates recorded under a unique - GridCoordinates node. - - The normal is outward oriented and its norms equals the area of the faces. - - Args: - zone (CGNSTree): Distributed 3D or 2D U-NGon CGNS Zone - Returns: - face_normal (array): Flat (interlaced) numpy array of face normal - - """ - cx, cy, cz = PT.Zone.coordinates(zone) - dist_coords = {'CoordinateX' : cx, 'CoordinateY': cy, 'CoordinateZ': cz} - vtx_distri = MT.getDistribution(zone, 'Vertex')[1] - - if PT.Zone.Type(zone) == "Unstructured": - if PT.Zone.has_ngon_elements(zone): - ngon_node = PT.Zone.NGonNode(zone) - face_vtx_idx = PT.get_child_from_name(ngon_node, 'ElementStartOffset')[1] - _face_vtx_idx = np.empty(face_vtx_idx.size, np.int32) - np.subtract(face_vtx_idx, face_vtx_idx[0], out=_face_vtx_idx) - face_vtx = PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1] - part_data = EP.block_to_part(dist_coords, vtx_distri, [face_vtx], comm) - coords = [part_data[f'Coordinate{key}'][0] for key in ['X', 'Y', 'Z']] - - return cpart_algo.compute_face_normal_u(_face_vtx_idx, *coords) - raise NotImplementedError("Only NGON zones are managed") - -@PT.check_is_label("Zone_t") -def compute_face_center(zone, comm): - """Compute the face center of a distributed zone. - - Input zone must have cartesian coordinates recorded under a unique - GridCoordinates node. - - Centers are computed using a basic average over the vertices of the faces. - - Args: - zone (CGNSTree): Distributed 3D or 2D U-NGon CGNS Zone - Returns: - face_normal (array): Flat (interlaced) numpy array of face centers - - """ - cx, cy, cz = PT.Zone.coordinates(zone) - dist_coords = {'CoordinateX' : cx, 'CoordinateY': cy, 'CoordinateZ': cz} - vtx_distri = MT.getDistribution(zone, 'Vertex')[1] - - if PT.Zone.Type(zone) == "Unstructured": - if PT.Zone.has_ngon_elements(zone): - ngon_node = PT.Zone.NGonNode(zone) - face_vtx_idx = PT.get_child_from_name(ngon_node, 'ElementStartOffset')[1] - _face_vtx_idx = np.empty(face_vtx_idx.size, np.int32) - np.subtract(face_vtx_idx, face_vtx_idx[0], out=_face_vtx_idx) - face_vtx = PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1] - part_data = EP.block_to_part(dist_coords, vtx_distri, [face_vtx], comm) - coords = [part_data[f'Coordinate{key}'][0] for key in ['X', 'Y', 'Z']] - - return _mean_coords_from_connectivity(_face_vtx_idx, *coords) - raise NotImplementedError("Only NGON zones are managed") - -@PT.check_is_label("Zone_t") -def compute_cell_center(zone, comm): - - if PT.Zone.Type(zone) == "Structured": - raise NotImplementedError("Only NGON zones are managed") - - cell_vtx_idx, cell_vtx = _cell_vtx_connectivity(zone, comm) - - cx, cy, cz = PT.Zone.coordinates(zone) - dist_coords = {'CoordinateX' : cx, 'CoordinateY': cy, 'CoordinateZ': cz} - vtx_distri = MT.getDistribution(zone, 'Vertex')[1] - - part_data = EP.block_to_part(dist_coords, vtx_distri, [cell_vtx], comm) - coords = [part_data[f'Coordinate{key}'][0] for key in ['X', 'Y', 'Z']] - - return _mean_coords_from_connectivity(cell_vtx_idx, *coords) \ No newline at end of file diff --git a/maia/algo/dist/matching_jns_tools.py b/maia/algo/dist/matching_jns_tools.py deleted file mode 100644 index 4963d75a..00000000 --- a/maia/algo/dist/matching_jns_tools.py +++ /dev/null @@ -1,240 +0,0 @@ -import mpi4py.MPI as MPI -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from .subset_tools import sort_dist_pointlist - -def gc_is_reference(gc_s, zone_path): - """ - Check if a structured 1to1 GC is the reference of its pair or not - The opposite GC is not needed to do that (paths and pointrange are - compared) - """ - zone_path_opp = PT.GridConnectivity.ZoneDonorPath(gc_s, PT.path_head(zone_path)) - if zone_path < zone_path_opp: - return True - elif zone_path > zone_path_opp: - return False - else: #Same zone path - pr = PT.get_child_from_name(gc_s, "PointRange")[1] - prd = PT.get_child_from_name(gc_s, "PointRangeDonor")[1] - bnd_axis = PT.Subset.normal_axis(gc_s) - bnd_axis_d = PT.Subset.normal_axis(PT.new_GridConnectivity1to1(point_range=prd)) - if bnd_axis < bnd_axis_d: - return True - elif bnd_axis > bnd_axis_d: - return False - else: #Same boundary axis - bnd_axis_val = np.abs(pr[bnd_axis,0]) - bnd_axis_val_d = np.abs(prd[bnd_axis_d,0]) - if bnd_axis_val < bnd_axis_val_d: - return True - elif bnd_axis_val > bnd_axis_val_d: - return False - else: #Same position in boundary axis - if np.sum(pr) < np.sum(prd): - return True - elif np.sum(pr) > np.sum(prd): - return False - raise ValueError("Unable to determine if node is reference") - -def _compare_pointrange(gc1, gc2): - """ - Compare a couple of grid_connectivity nodes and return True - if the PointList and PointListDonor are equals, even - if the symmetry is not respected - """ - gc1_pr = PT.get_child_from_name(gc1, 'PointRange')[1] - gc1_prd = PT.get_child_from_name(gc1, 'PointRangeDonor')[1] - gc2_pr = PT.get_child_from_name(gc2, 'PointRange')[1] - gc2_prd = PT.get_child_from_name(gc2, 'PointRangeDonor')[1] - if gc1_pr.shape != gc2_prd.shape or gc2_pr.shape != gc1_prd.shape: - return False - - return (np.sort(gc1_pr) == np.sort(gc2_prd)).all() and (np.sort(gc2_pr) == np.sort(gc1_prd)).all() - -def _compare_pointlist(gc1, gc2): - """ - Compare a couple of grid_connectivity nodes and return True - if the PointList and PointListDonor are symmetrically equals - """ - gc1_pl = np.asarray(PT.get_child_from_name(gc1, 'PointList')[1]) - gc1_pld = np.asarray(PT.get_child_from_name(gc1, 'PointListDonor')[1]) - gc2_pl = np.asarray(PT.get_child_from_name(gc2, 'PointList')[1]) - gc2_pld = np.asarray(PT.get_child_from_name(gc2, 'PointListDonor')[1]) - if gc1_pl.shape != gc2_pld.shape or gc2_pl.shape != gc1_pld.shape: - return False - return (np.all(gc1_pl == gc2_pld) and np.all(gc2_pl == gc1_pld)) - -def _create_local_match_table(gc_list, gc_paths): - """ - Iterate over a list of joins to compare the PointList / PointListDonor - and retrieve the pairs of matching joins - """ - nb_joins = len(gc_list) - local_match_table = np.zeros((nb_joins, nb_joins), dtype=bool) - - for igc, gc in enumerate(gc_list): - current_path = gc_paths[igc] - current_base = current_path.split('/')[0] - opp_path = PT.GridConnectivity.ZoneDonorPath(gc, current_base) - candidates = [i for i,path in enumerate(gc_paths) if - (path==opp_path and PT.GridConnectivity.ZoneDonorPath(gc_list[i], path.split('/')[0]) == current_path)] - gc_has_pl = PT.get_child_from_name(gc, 'PointList') is not None - for j in candidates: - candidate_has_pl = PT.get_child_from_name(gc_list[j], 'PointList') is not None - if gc_has_pl and candidate_has_pl: - local_match_table[igc][j] = _compare_pointlist(gc, gc_list[j]) - elif not gc_has_pl and not candidate_has_pl: - local_match_table[igc][j] = _compare_pointrange(gc, gc_list[j]) - return local_match_table - -def add_joins_donor_name(dist_tree, comm, force=False): - """ - For each GridConnectivity_t node found in the dist_tree, find the - opposite GC node and create the GridConnectivityDonorName node - GC Node must have either PointList/PointListDonor arrays or - PointRange/PointRangeDonor arrays, not both. - If force=True, donor are recomputed, else the computation is - made only if there are not in tree - """ - - gc_list = [] - gc_paths = [] - # > First pass to collect joins - match1to1 = lambda n : PT.get_label(n) in ['GridConnectivity1to1_t', 'GridConnectivity_t'] \ - and PT.GridConnectivity.is1to1(n) - query = ["CGNSBase_t", "Zone_t", "ZoneGridConnectivity_t", match1to1] - - if force: - for gc in PT.iter_children_from_predicates(dist_tree, query): - PT.rm_children_from_name(gc, 'GridConnectivityDonorName') - - for nodes in PT.iter_children_from_predicates(dist_tree, query, ancestors=True): #get_node_from_path is slower, dont use it - gc_node = nodes[-1] - if PT.get_child_from_name(gc_node, 'GridConnectivityDonorName') is None: - # Skip nodes that already have their DonorName - gc_list.append(gc_node) - gc_paths.append('/'.join([PT.get_name(node) for node in nodes[:2]])) - - if len(gc_list) == 0: - return - - local_match_table = _create_local_match_table(gc_list, gc_paths) - - global_match_table = np.empty(local_match_table.shape, dtype=bool) - comm.Allreduce(local_match_table, global_match_table, op=MPI.LAND) - assert(np.all(np.sum(global_match_table, axis=0) == 1)) - - opp_join_id = np.where(global_match_table)[1] - for gc_id, (gc, opp_id) in enumerate(zip(gc_list, opp_join_id)): - PT.new_node("GridConnectivityDonorName", "Descriptor_t", PT.get_name(gc_list[opp_id]), parent=gc) - -def get_jn_donor_path(dist_tree, jn_path): - """ - Return the patch of the matching jn in the tree. GridConnectivityDonorName must exists. - """ - cur_jn = PT.get_node_from_path(dist_tree, jn_path) - base_name, zone_name, zgc_name, jn_name = jn_path.split('/') - opp_zone_path = PT.GridConnectivity.ZoneDonorPath(cur_jn, base_name) - gc_donor_name = PT.get_child_from_name(cur_jn, "GridConnectivityDonorName") - if gc_donor_name is None: - raise RuntimeError(f"No GridConnectivityDonorName found in GC {jn_path}") - opp_gc_name = PT.get_value(gc_donor_name) - - opp_zone = PT.get_node_from_path(dist_tree, opp_zone_path) - opp_zgc = PT.get_child_from_label(opp_zone, "ZoneGridConnectivity_t") - return f"{opp_zone_path}/{PT.get_name(opp_zgc)}/{opp_gc_name}" - -def update_jn_name(dist_tree, jn_path, new_name): - """ - Rename a 1to1 GC and update the opposite GridConnectivityDonorName. - """ - cur_jn = PT.get_node_from_path(dist_tree, jn_path) - opp_jn = PT.get_node_from_path(dist_tree, get_jn_donor_path(dist_tree, jn_path)) - opp_gc_name_n = PT.get_child_from_name(opp_jn, "GridConnectivityDonorName") - PT.set_name(cur_jn, new_name) - PT.set_value(opp_gc_name_n, new_name) - -def get_matching_jns(dist_tree, select_func=None): - """ - Return the list of pairs of matching jns - """ - if select_func is None: - gc_query = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] \ - and PT.GridConnectivity.is1to1(n) - else: - gc_query = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] \ - and PT.GridConnectivity.is1to1(n) \ - and select_func(n) - - query = ['CGNSBase_t', 'Zone_t', 'ZoneGridConnectivity_t', gc_query] - - # Retrieve interfaces pathes and call function - jn_pairs = [] - for jn_path in PT.predicates_to_paths(dist_tree, query): - opp_jn_path = get_jn_donor_path(dist_tree, jn_path) - pair = tuple(sorted([jn_path, opp_jn_path])) - if not pair in jn_pairs: - jn_pairs.append(pair) - return jn_pairs - -def copy_donor_subset(dist_tree): - """ - Retrieve for each 1to1 GridConnectivity_t node the opposite - pointlist in the tree. This assume that GridConnectivityDonorName were added and index distribution - was identical for two related gc nodes - """ - gc_predicates = ['CGNSBase_t', 'Zone_t', 'ZoneGridConnectivity_t', \ - lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] and - PT.GridConnectivity.is1to1(n)] - - for jn_path in PT.predicates_to_paths(dist_tree, gc_predicates): - opp_jn_path = get_jn_donor_path(dist_tree, jn_path) - cur_jn = PT.get_node_from_path(dist_tree, jn_path) - opp_jn = PT.get_node_from_path(dist_tree, opp_jn_path) - opp_patch = PT.deep_copy(PT.Subset.getPatch(opp_jn)) - PT.set_name(opp_patch, PT.get_name(opp_patch) + 'Donor') - PT.rm_children_from_name(cur_jn, PT.get_name(opp_patch)) - PT.add_child(cur_jn, opp_patch) - - -def store_interfaces_ids(dist_tree): - """ - Attribute to each 1to1 pair a unique interface id. GridConnectivityDonorName must have been added in the tree. - Store this id and the position (first or second) in disttree. - Note : this function does not manage (for now?) location: two jns at different interface - will have a different id - """ - matching_pairs = get_matching_jns(dist_tree) - for i, matching_pair in enumerate(matching_pairs): - for j,jn_path in enumerate(matching_pair): - jn = PT.get_node_from_path(dist_tree, jn_path) - PT.new_node("DistInterfaceId", "DataArray_t", i+1, parent=jn) - PT.new_node("DistInterfaceOrd", "DataArray_t", j, parent=jn) - -def clear_interface_ids(dist_tree): - """ - Remove DistInterfaceId nodes created on GC_t - """ - gc_query = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - for gc in PT.iter_children_from_predicates(dist_tree, ['CGNSBase_t', 'Zone_t', 'ZoneGridConnectivity_t', gc_query]): - PT.rm_children_from_name(gc, 'DistInterfaceId') - PT.rm_children_from_name(gc, 'DistInterfaceOrd') - -def sort_jn_pointlist(dist_tree, comm): - for jn_pair in get_matching_jns(dist_tree): - gc = PT.get_node_from_path(dist_tree, jn_pair[0]) - gc_opp = PT.get_node_from_path(dist_tree, jn_pair[1]) - - # Update current - sort_dist_pointlist(gc, comm) - - # Update donor - PT.update_child(gc_opp, 'PointList', value=PT.get_value(PT.get_node_from_name(gc,'PointListDonor'))) - PT.update_child(gc_opp, 'PointListDonor', value=PT.get_value(PT.get_node_from_name(gc,'PointList'))) - MT.newDistribution({'Index': PT.get_value(MT.getDistribution(gc,'Index'))}, gc_opp) - - diff --git a/maia/algo/dist/merge.py b/maia/algo/dist/merge.py deleted file mode 100644 index 0fab51db..00000000 --- a/maia/algo/dist/merge.py +++ /dev/null @@ -1,729 +0,0 @@ -import numpy as np -from re import sub -from Pypdm import Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.sids as sids -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.utils import py_utils, np_utils, par_utils, as_pdm_gnum - -from maia.algo.dist import matching_jns_tools as MJT -from maia.algo.dist import concat_nodes as GN -from maia.algo.dist import vertex_list as VL -from maia.transfer import protocols as EP - -def _append_or_create(d, key, val): - try: - d[key].append(val) - except KeyError: - d[key] = [val] - -def camel_case(s): - return sub(r"(_|-)+", " ", s).title().replace(" ", "") - -def merge_all_zones_from_families(tree, comm, **kwargs): - """Apply merge_zones_from_family to each family of the tree""" - family_names = [PT.get_name(node) for node in \ - PT.iter_nodes_from_label(tree, 'Family_t', depth=2)] - for family_name in family_names: - merge_zones_from_family(tree, family_name, comm, **kwargs) - -def merge_zones_from_family(tree, family_name, comm, **kwargs): - """Merge the zones belonging to the given family into a single one. - - See :func:`merge_zones` for full documentation. - - Args: - tree (CGNSTree): Input distributed tree - family_name (str): Name of the family (read from ``FamilyName_t`` node) - used to select the zones. - comm (MPIComm) : MPI communicator - kwargs: any argument of :func:`merge_zones`, excepted output_path - - See also: - Function ``merge_all_zones_from_families(tree, comm, **kwargs)`` does - this operation for all the ``Family_t`` nodes of the input tree. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #merge_zones_from_family@start - :end-before: #merge_zones_from_family@end - :dedent: 2 - """ - match_fam = lambda m: PT.get_child_from_label(m, 'FamilyName_t') is not None and \ - PT.get_value(PT.get_child_from_label(m, 'FamilyName_t')) == family_name - - is_zone_with_fam = lambda n: PT.get_label(n) == 'Zone_t' and match_fam(n) - - zone_paths = PT.predicates_to_paths(tree, ['CGNSBase_t', is_zone_with_fam]) - if zone_paths: - base_name = zone_paths[0].split('/')[0] - zone_name = camel_case(family_name) - if zone_name == family_name: - zone_name = zone_name.lower() - merge_zones(tree, zone_paths, comm, output_path=f'{base_name}/{zone_name}', **kwargs) - -def merge_connected_zones(tree, comm, **kwargs): - """Detect all the zones connected through 1to1 matching jns and merge them. - - See :func:`merge_zones` for full documentation. - - Args: - tree (CGNSTree): Input distributed tree - comm (MPIComm) : MPI communicator - kwargs: any argument of :func:`merge_zones`, excepted output_path - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #merge_connected_zones@start - :end-before: #merge_connected_zones@end - :dedent: 2 - """ - MJT.add_joins_donor_name(tree, comm) - grouped_zone_paths = PT.Tree.find_connected_zones(tree) - - for i, zone_paths in enumerate(grouped_zone_paths): - zone_paths_u = [path for path in zone_paths \ - if sids.Zone.Type(PT.get_node_from_path(tree, path)) == 'Unstructured'] - base = zone_paths[0].split('/')[0] - merge_zones(tree, zone_paths_u, comm, output_path=f'{base}/mergedZone{i}', **kwargs) - -def merge_zones(tree, zone_paths, comm, output_path=None, subset_merge='name', concatenate_jns=True): - """Merge the given zones into a single one. - - Input tree is modified inplace : original zones will be removed from the tree and replaced - by the merged zone. Merged zone is added with name *MergedZone* under the first involved Base - except if output_path is not None : in this case, the provided path defines the base and zone name - of the merged block. - - Subsets of the merged block can be reduced thanks to subset_merge parameter: - - - ``None`` : no reduction occurs : all subset of all original zones remains on merged zone, with a - numbering suffix. - - ``'name'`` : Subset having the same name on the original zones (within a same label) produces - and unique subset on the output merged zone. - - Only unstructured-NGon trees are supported, and interfaces between the zones - to merge must have a FaceCenter location. - - Args: - tree (CGNSTree): Input distributed tree - zone_paths (list of str): List of path (BaseName/ZoneName) of the zones to merge. - Wildcard ``*`` are allowed in BaseName and/or ZoneName. - comm (MPIComm): MPI communicator - output_path (str, optional): Path of the output merged block. Defaults to None. - subset_merge (str, optional): Merging strategy for the subsets. Defaults to 'name'. - concatenate_jns (bool, optional): if True, reduce the multiple 1to1 matching joins related - to the merged_zone to a single one. Defaults to True. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #merge_zones@start - :end-before: #merge_zones@end - :dedent: 2 - """ - # Transform wildcard into concrete path - replace_super_wildcard = lambda p: '*/*' if p == '*' else p - zone_paths = [replace_super_wildcard(p) for p in zone_paths] - zone_paths = PT.concretize_paths(tree, zone_paths, ['CGNSBase_t', 'Zone_t']) - - assert all([sids.Zone.Type(PT.get_node_from_path(tree, path)) == 'Unstructured' for path in zone_paths]) - #Those one will be needed for jn recovering - MJT.add_joins_donor_name(tree, comm) - - #Force full donor name, otherwise it is hard to reset jns - sids.enforceDonorAsPath(tree) - - # We create a tree including only the zones to merge to speed up some operations - masked_tree = PT.new_CGNSTree() - for zone_path in zone_paths: - base_n, zone_n = zone_path.split('/') - masked_base = PT.update_child(masked_tree, base_n, 'CGNSBase_t') - PT.add_child(masked_base, PT.get_node_from_path(tree, zone_path)) - - #Remove from input tree at the same time - PT.rm_node_from_path(tree, zone_path) - - #Merge zones - merged_zone = _merge_zones(masked_tree, comm, subset_merge) - - #Add output - if output_path is None: - output_base = PT.get_node_from_path(tree, zone_paths[0].split('/')[0]) - else: - output_base = PT.get_node_from_path(tree, output_path.split('/')[0]) - if output_base is None: - output_base = PT.new_CGNSBase(output_path.split('/')[0], cell_dim=3, phy_dim=3, parent=tree) - PT.set_name(merged_zone, output_path.split('/')[1]) - PT.add_child(output_base, merged_zone) - - #First we have to retrieve PLDonor for external jn and update opposite zones - merged_zone_path = PT.get_name(output_base) + '/' + PT.get_name(merged_zone) - jn_to_pl = {} - for jn_path in PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t/ZoneGridConnectivity_t/GridConnectivity_t'): - gc = PT.get_node_from_path(tree, jn_path) - jn_to_pl[jn_path] = \ - (PT.get_child_from_name(gc, 'PointList')[1], PT.get_child_from_name(gc, 'PointListDonor')[1], MT.getDistribution(gc)) - - # Update opposite names when going to opp zone (intrazone have been caried before) - for zgc, gc in PT.get_children_from_predicates(merged_zone, ['ZoneGridConnectivity_t', 'GridConnectivity_t'], ancestors=True): - if PT.get_value(gc) not in zone_paths: - opp_path = MJT.get_jn_donor_path(tree, f"{merged_zone_path}/{zgc[0]}/{gc[0]}") - opp_gc = PT.get_node_from_path(tree, opp_path) - opp_gc_donor_name = PT.get_child_from_name(opp_gc, 'GridConnectivityDonorName') #TODO factorize - PT.set_value(opp_gc_donor_name, gc[0]) - #Now all donor names are OK - - for zone_path in PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t'): - is_merged_zone = zone_path == merged_zone_path - zone = PT.get_node_from_path(tree, zone_path) - for zgc, gc in PT.get_children_from_predicates(zone, ['ZoneGridConnectivity_t', 'GridConnectivity_t'], ancestors=True): - #Update name and PL - if PT.get_value(gc) in zone_paths: #Can be: jn from non concerned zone to merged zones or periodic from merged zones - PT.set_value(gc, merged_zone_path) - jn_path = f"{zone_path}/{PT.get_name(zgc)}/{PT.get_name(gc)}" - jn_path_opp= MJT.get_jn_donor_path(tree, jn_path) - # Copy and permute pl/pld only for all the zones != merged zone OR for one gc over two for - # merged zone - if not is_merged_zone or jn_path_opp < jn_path: - PT.update_child(gc, 'PointList' , 'IndexArray_t', jn_to_pl[jn_path_opp][1]) - PT.update_child(gc, 'PointListDonor', 'IndexArray_t', jn_to_pl[jn_path_opp][0]) - PT.rm_children_from_name(gc, ":CGNS#Distribution") - PT.add_child(gc, jn_to_pl[jn_path_opp][2]) - - if concatenate_jns: - GN.concatenate_jns(tree, comm) - - # Transfert some nodes on the merged zone, only if they exist everywhere and have same value - merge_me = lambda n: PT.get_label(n) in ['FamilyName_t', 'AdditionalFamilyName_t'] - if len(zone_paths) > 0: - zone = PT.get_node_from_path(masked_tree, zone_paths[0]) - common = {(PT.get_name(n), PT.get_label(n), PT.get_value(n)) \ - for n in PT.iter_children_from_predicate(zone, merge_me)} - for zone_path in zone_paths[1:]: - zone = PT.get_node_from_path(masked_tree, zone_path) - # Use set intersection to eliminate nodes that does not appear on this zone - common = common & {(PT.get_name(n), PT.get_label(n), PT.get_value(n)) \ - for n in PT.iter_children_from_predicate(zone, merge_me)} - for c in sorted(common): #Sort to garantie same insertion order across mpi ranks - PT.new_child(merged_zone, name=c[0], label=c[1], value=c[2]) - - # Cleanup empty bases - to_remove = [] - for base in PT.get_children_from_label(tree, 'CGNSBase_t'): - if len(PT.get_children_from_label(base, 'Zone_t')) == 0: - to_remove.append(PT.get_name(base)) - for base_n in to_remove: - PT.rm_children_from_name(tree, base_n) - -def _add_zone_suffix(zones, query): - """Util function prefixing all the nodes founds by a query by the number of the zone""" - for izone, zone in enumerate(zones): - for node in PT.get_children_from_predicates(zone, query): - PT.set_name(node, PT.get_name(node) + f".{izone}") - # Also update internal (periodic) joins with new donor name - if PT.get_child_from_name(node, '__maia_jn_update__') is not None: - opp_domain_id = PT.get_child_from_name(node, '__maia_jn_update__')[1][0] - donor_name_node = PT.get_child_from_name(node, "GridConnectivityDonorName") - PT.set_value(donor_name_node, f"{PT.get_value(donor_name_node)}.{opp_domain_id}") - -def _rm_zone_suffix(zones, query): - """Util function removing the number of the zone in all the nodes founds by a query - Use it to cleanup after _add_zone_suffix has been applied""" - for zone in zones: - for node in PT.get_children_from_predicates(zone, query): - PT.set_name(node, '.'.join(PT.get_name(node).split('.')[:-1])) - -def _merge_zones(tree, comm, subset_merge_strategy='name'): - """ - Tree must contain *only* the zones to merge. We use a tree instead of a list of zone because it's easier - to retrieve opposites zones througt joins. Interface beetween zones shall be described by faces - """ - - zone_paths = PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t') - n_zone = len(zone_paths) - zones = PT.get_all_Zone_t(tree) - assert min([sids.Zone.Type(zone) == 'Unstructured' for zone in zones]) == True - - zone_to_id = {path : i for i, path in enumerate(zone_paths)} - - is_perio = lambda n : PT.get_child_from_label(n, 'GridConnectivityProperty_t') is not None - gc_query = ['ZoneGridConnectivity_t', \ - lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - and sids.Subset.GridLocation(n) == 'FaceCenter'] - - # JNs to external zones must be excluded from vertex list computing - tree_vl = PT.shallow_copy(tree) - for base, zone in PT.get_children_from_predicates(tree_vl, ['CGNSBase_t', 'Zone_t'], ancestors=True): - for zgc, gc in PT.get_children_from_predicates(zone, gc_query, ancestors=True): - if PT.GridConnectivity.ZoneDonorPath(gc, PT.get_name(base)) not in zone_to_id: - PT.rm_child(zgc, gc) - VL.generate_jns_vertex_list(tree_vl, comm, have_isolated_faces=True) - #Reput in tree - for zone_path in zone_paths: - zone = PT.get_node_from_path(tree, zone_path) - zone_vl = PT.get_node_from_path(tree_vl, zone_path) - for zgc in PT.get_children_from_label(zone, 'ZoneGridConnectivity_t'): - zgc_vl = PT.get_child_from_name(zone_vl, PT.get_name(zgc)) - for gc_vl in PT.get_children_from_predicate(zgc_vl, lambda n: PT.get_label(n) == 'GridConnectivity_t' \ - and sids.Subset.GridLocation(n) == 'Vertex'): - PT.add_child(zgc, gc_vl) - - # Collect interface data - interface_dn_f = [] - interface_ids_f = [] - interface_dom = [] - interface_dn_v = [] - interface_ids_v = [] - for zone_path, zone in zip(zone_paths, zones): - base_name, zone_name = zone_path.split('/') - for zgc, gc in PT.get_children_from_predicates(zone, gc_query, ancestors=True): - opp_zone_path = PT.GridConnectivity.ZoneDonorPath(gc, base_name) - if opp_zone_path in zone_to_id: - if is_perio(gc): - PT.new_node('__maia_jn_update__', 'UserDefinedData_t', value=zone_to_id[opp_zone_path], parent=gc) - else: - PT.new_node('__maia_merge__', 'Descriptor_t', parent=gc) - gc_path = f"{zone_path}/{PT.get_name(zgc)}/{PT.get_name(gc)}" - gc_path_opp = MJT.get_jn_donor_path(tree, gc_path) - if PT.get_child_from_name(gc, '__maia_merge__') is not None and gc_path < gc_path_opp: - interface_dom.append((zone_to_id[zone_path], zone_to_id[opp_zone_path])) - - pl = as_pdm_gnum(PT.get_child_from_name(gc, 'PointList')[1][0]) - pld = as_pdm_gnum(PT.get_child_from_name(gc, 'PointListDonor')[1][0]) - - interface_dn_f.append(pl.size) - interface_ids_f.append(np_utils.interweave_arrays([pl,pld])) - - # Find corresponding vertex - gc_vtx = PT.get_child_from_name(zgc, f'{PT.get_name(gc)}#Vtx') - pl_v = PT.get_child_from_name(gc_vtx, 'PointList')[1][0] - pld_v = PT.get_child_from_name(gc_vtx, 'PointListDonor')[1][0] - interface_dn_v.append(pl_v.size) - interface_ids_v.append(np_utils.interweave_arrays([pl_v,pld_v])) - - # Generate interfaces - graph_idx, graph_ids, graph_dom = PDM.interface_to_graph(\ - len(interface_dn_v), False, interface_dn_v, interface_ids_v, interface_dom, comm) - graph_dict_v = {'graph_idx' : graph_idx, 'graph_ids' : graph_ids, 'graph_dom' : graph_dom} - - graph_idx, graph_ids, graph_dom = PDM.interface_to_graph(\ - len(interface_dn_f), False, interface_dn_f, interface_ids_f, interface_dom, comm) - graph_dict_f = {'graph_idx' : graph_idx, 'graph_ids' : graph_ids, 'graph_dom' : graph_dom} - - # Empty graph for cells - graph_dict_c = {'graph_idx' : np.array([0], np.int32), - 'graph_ids' : np.empty(0, pdm_dtype), - 'graph_dom' : np.empty(0, np.int32)} - - # Collect distributions - entities = ['Vertex', 'Face', 'Cell'] - blocks_distri_l = {entity : [] for entity in entities} - selected_l = {entity : [] for entity in entities} - for zone in zones: - for entity in entities: - if entity == 'Face': - distri = as_pdm_gnum(MT.getDistribution(sids.Zone.NGonNode(zone), 'Element')[1]) - else: - distri = as_pdm_gnum(MT.getDistribution(zone, entity)[1]) - blocks_distri_l[entity].append(par_utils.partial_to_full_distribution(distri, comm)) - selected_l[entity].append(np.arange(distri[0], distri[1], dtype=pdm_dtype)+1) - - # Create merge protocols - mbm_vtx = PDM.MultiBlockMerge(n_zone, blocks_distri_l['Vertex'], selected_l['Vertex'], graph_dict_v, comm) - mbm_face = PDM.MultiBlockMerge(n_zone, blocks_distri_l['Face' ], selected_l['Face' ], graph_dict_f, comm) - mbm_cell = PDM.MultiBlockMerge(n_zone, blocks_distri_l['Cell' ], selected_l['Cell' ], graph_dict_c, comm) - all_mbm = {'Vertex' : mbm_vtx, 'Face' : mbm_face, 'Cell' : mbm_cell} - - merged_distri_vtx = mbm_vtx .get_merged_distri() - merged_distri_face = mbm_face.get_merged_distri() - merged_distri_cell = mbm_cell.get_merged_distri() - - zone_dims = np.array([[merged_distri_vtx[-1], merged_distri_cell[-1], 0]], order='F') - merged_zone = PT.new_Zone('MergedZone', size=zone_dims, type='Unstructured') - - # NGon - PT.add_child(merged_zone, _merge_ngon(all_mbm, tree, comm)) - - # Generate NFace (TODO) - pass - - - loc_without_pl = lambda n, loc : sids.Subset.GridLocation(n) == loc and PT.get_node_from_name(n, 'PointList') is None - # Merge all mesh data - vtx_data_queries = [ - ['GridCoordinates_t'], - [lambda n: PT.get_label(n) == 'FlowSolution_t' and loc_without_pl(n, 'Vertex')], - [lambda n: PT.get_label(n) == 'DiscreteData_t' and loc_without_pl(n, 'Vertex')], - ] - cell_data_queries = [ - [lambda n: PT.get_label(n) == 'FlowSolution_t' and loc_without_pl(n, 'CellCenter')], - [lambda n: PT.get_label(n) == 'DiscreteData_t' and loc_without_pl(n, 'CellCenter')], - ] - _merge_allmesh_data(mbm_vtx, zones, merged_zone, vtx_data_queries) - _merge_allmesh_data(mbm_cell, zones, merged_zone, cell_data_queries) - - _merge_pls_data(all_mbm, zones, merged_zone, comm, subset_merge_strategy) - - MT.newDistribution({'Vertex' : par_utils.full_to_partial_distribution(merged_distri_vtx, comm), - 'Cell' : par_utils.full_to_partial_distribution(merged_distri_cell, comm)}, - merged_zone) - return merged_zone - -def _merge_allmesh_data(mbm, zones, merged_zone, data_queries): - """ - Merge the all DataArray supported by allCells or allVertex (depending on query and mbm), - found under each of data_query (query must start from zone node), from input zones - to merged_zone. - """ - - to_merge = dict() - - for query in data_queries: - for zone in zones: - #For global data, we should have only one parent - for node, data in PT.get_children_from_predicates(zone, query + ['DataArray_t'], ancestors=True): - dic_path = PT.get_name(node) + '/' + PT.get_name(data) - _append_or_create(to_merge, dic_path, data[1]) - - merged = {key : mbm.merge_field(datas) for key, datas in to_merge.items()} - - - additional_types = ['GridLocation_t', 'Descriptor_t', 'DataClass_t', 'DimensionalUnits_t'] - for query in data_queries: - #Use zone 0 to get node type and value. Nodes must be know in every zone - for node in PT.get_children_from_predicates(zones[0], query): - m_node = PT.update_child(merged_zone, PT.get_name(node), PT.get_label(node), PT.get_value(node)) - for data in PT.iter_children_from_label(node, 'DataArray_t'): - PT.new_DataArray(data[0], merged[PT.get_name(node) + '/' + PT.get_name(data)], parent=m_node) - for type in additional_types: - for sub_node in PT.iter_children_from_label(node, type): - PT.add_child(m_node, sub_node) - -def _merge_pls_data(all_mbm, zones, merged_zone, comm, merge_strategy='name'): - """ - Wrapper to perform a merge of the following subset nodes (when having a PointList) : - FlowSolution_t, DiscreteData_t, ZoneSubRegion_t, BC_t, GridConnectivity_t, BCDataSet_t - from the input zones to the merged zone merged_zone. - - If merged_strategy=='name', the nodes subset set having the same name on different zones are merged - into a single one. - Otherwise, force to keep one subset_node per input zone to let the user manage its merge. - Merging by name is not performed for GridConnectivity_t - """ - #In each case, we need to collect all the nodes, since some can be absent of a given zone - has_pl = lambda n : PT.get_child_from_name(n, 'PointList') is not None - jn_to_keep = lambda n : PT.get_label(n) == 'GridConnectivity_t' and sids.Subset.GridLocation(n) == 'FaceCenter'\ - and PT.get_child_from_name(n, '__maia_merge__') is None - - #Order : FlowSolution/DiscreteData/ZoneSubRegion, BC, BCDataSet, GridConnectivity_t, - all_subset_queries = [ - [lambda n : PT.get_label(n) in ['FlowSolution_t', 'DiscreteData_t', 'ZoneSubRegion_t'] and has_pl(n)], - ['ZoneBC_t', 'BC_t'], - ['ZoneBC_t', 'BC_t', lambda n : PT.get_label(n) == 'BCDataSet_t' and has_pl(n)], - ['ZoneGridConnectivity_t', jn_to_keep] - ] - - all_data_queries = [ - ['DataArray_t'], - [lambda n : PT.get_label(n) == 'BCDataSet_t' and not has_pl(n), 'BCData_t', 'DataArray_t'], - ['BCData_t', 'DataArray_t'], - ['PointListDonor'], - ] - - # Trick to avoid spectific treatment of ZoneSubRegions (add PL) - for zone in zones: - for zsr in PT.iter_children_from_label(zone, 'ZoneSubRegion_t'): - #Copy PL when related to bc/gc to avoid specific treatement - if PT.get_child_from_name(zsr, 'BCRegionName') is not None or \ - PT.get_child_from_name(zsr, 'GridConnectivityRegionName') is not None: - related = PT.get_node_from_path(zone, sids.Subset.ZSRExtent(zsr, zone)) - PT.add_child(zsr, PT.get_child_from_name(related, 'PointList')) - - i_query = 0 - for query, rules in zip(all_subset_queries, all_data_queries): - if merge_strategy != 'name' or query[0] == 'ZoneGridConnectivity_t': - _add_zone_suffix(zones, query) - if merge_strategy != 'name' and i_query == 2: #For BCDataSet, we should also update BC name - _add_zone_suffix(zones, query[:-1]) - collected_paths = [] - #Collect - for zone in zones: - for nodes in PT.get_children_from_predicates(zone, query, ancestors=True): - py_utils.append_unique(collected_paths, '/'.join([PT.get_name(node) for node in nodes])) - #Merge and add to output - for pl_path in collected_paths: - #We have to retrieve a zone knowing this node to deduce the kind of parent nodes and gridLocation - master_node = None - for zone in zones: - if PT.get_node_from_path(zone, pl_path) is not None: - master_node = zone - break - assert master_node is not None - parent = merged_zone - location = sids.Subset.GridLocation(PT.get_node_from_path(master_node, pl_path)) - mbm = all_mbm[location.split('Center')[0]] - merged_pl = _merge_pl_data(mbm, zones, pl_path, location, rules, comm) - #Rebuild structure until last node - for child_name in pl_path.split('/')[:-1]: - master_node = PT.get_child_from_name(master_node, child_name) - parent = PT.update_child(parent, child_name, PT.get_label(master_node), PT.get_value(master_node)) - - PT.add_child(parent, merged_pl) - if merge_strategy != 'name'or query[0] == 'ZoneGridConnectivity_t': - _rm_zone_suffix(zones, query) - if merge_strategy != 'name' and i_query == 2: #For BCDataSet, we should also update BC name - _rm_zone_suffix(zones, query[:-1]) - i_query += 1 - - # Trick to avoid spectific treatment of ZoneSubRegions (remove PL on original zones) - for zone in zones: - for zsr in PT.iter_children_from_label(zone, 'ZoneSubRegion_t'): - if PT.get_child_from_name(zsr, 'BCRegionName') is not None or \ - PT.get_child_from_name(zsr, 'GridConnectivityRegionName') is not None: - PT.rm_children_from_name(zsr, 'PointList*') - # Since link may be broken in merged zone, it is safer to remove it - for zsr in PT.iter_children_from_label(merged_zone, 'ZoneSubRegion_t'): - PT.rm_children_from_name(zsr, 'BCRegionName') - PT.rm_children_from_name(zsr, 'GridConnectivityRegionName') - -def _equilibrate_data(data, comm, distri=None, distri_full=None): - if distri_full is None: - if distri is None: - first = next(iter(data.values())) - distri_full = par_utils.gather_and_shift(first.size, comm, pdm_dtype) - else: - distri_full = par_utils.partial_to_full_distribution(distri, comm) - - ideal_distri = par_utils.uniform_distribution(distri_full[-1], comm) - dist_data = EP.block_to_block(data, distri_full, ideal_distri, comm) - - return ideal_distri, dist_data - - -def _merge_pl_data(mbm, zones, subset_path, loc, data_query, comm): - """ - Internal function used by _merge_zones to produce a merged node from the zones to merge - and the path to a subset node (having a PL) - Subset nodes comming from different zones with the same path will be merged - Also merge all the nodes found under the data_query query (starting from subset_node) - requested in data_queries list - - Return the merged subset node - """ - - ref_node = None - - has_data = [] - strides = [] - all_datas = {} - for i, zone in enumerate(zones): - node = PT.get_node_from_path(zone, subset_path) - if loc == 'Vertex': - distri_ptb = MT.getDistribution(zone, 'Vertex')[1] - elif loc == 'FaceCenter': - distri_ptb = MT.getDistribution(sids.Zone.NGonNode(zone), 'Element')[1] - elif loc == 'CellCenter': - distri_ptb = MT.getDistribution(zone, 'Cell')[1] - if node is not None: - ref_node = node #Take any node as reference, to build name/type/value of merged node - - pl = PT.get_child_from_name(node, 'PointList')[1][0] - part_data = {'PL' : [pl]} - for nodes in PT.get_children_from_predicates(node, data_query, ancestors=True): - path = '/'.join([PT.get_name(node) for node in nodes]) - data_n = nodes[-1] - data = data_n[1] - if data_n[1].ndim > 1: - assert data_n[1].ndim == 2 and PT.get_label(data_n) == 'IndexArray_t' - for dim in range(data_n[1].shape[0]): #Manage U (1,N) or S (3,N) PL - _append_or_create(part_data, f'{path}_{dim}', np.ascontiguousarray(data_n[1][dim])) - else: - _append_or_create(part_data, path, data) - #TODO maybe it is just a BtB -- nope because we want to reorder; but we could do one with all pl at once - dist_data = EP.part_to_block(part_data, distri_ptb, [pl], comm) - - stride = np.zeros(distri_ptb[1] - distri_ptb[0], np.int32) - stride[dist_data['PL'] - distri_ptb[0] - 1] = 1 - - has_data.append(True) - strides.append(stride) - for data_path, data in dist_data.items(): - _append_or_create(all_datas, data_path, data) - - else: - has_data.append(False) - strides.append(np.zeros(distri_ptb[1] - distri_ptb[0], np.int32)) - - #Fill data for void zones - for data_path, datas in all_datas.items(): - zero_data = np.empty(0, datas[0].dtype) - data_it = iter(datas) - updated_data = [next(data_it) if _has_data else zero_data for i,_has_data in enumerate(has_data)] - all_datas[data_path] = updated_data - - pl_data = all_datas.pop('PL') - _, merged_pl = mbm.merge_and_update(mbm, [as_pdm_gnum(pl) for pl in pl_data], strides) - merged_data = {'PointList' : merged_pl} - - # For periodic jns of zones to merge, PointListDonor must be transported and updated. - # Otherwise, it must just be transported to new zone - if PT.get_node_from_name(ref_node, '__maia_jn_update__') is not None: - opp_dom = PT.get_node_from_name(ref_node, '__maia_jn_update__')[1][0] - pld_data = all_datas.pop('PointListDonor_0') #Since we merge U zones we should have only 1D-PL - block_datas = [as_pdm_gnum(pld) for pld in pld_data] - block_domains = [opp_dom*np.ones(pld.size, np.int32) for pld in pld_data] - merged_data['PointListDonor_0'] = mbm.merge_and_update(mbm, block_datas, strides, block_domains)[1] - - merged_data.update({path : mbm.merge_field(datas, strides)[1] for path, datas in all_datas.items()}) - - # Data is merged, but distributed using pl distri. We do a BtB to re equilibrate it - merged_pl_distri, merged_data = _equilibrate_data(merged_data, comm) - - #Creation of node - merged_node = PT.new_node(PT.get_name(ref_node), PT.get_label(ref_node), PT.get_value(ref_node)) - PT.new_IndexArray(value=merged_data['PointList'].reshape((1, -1), order='F'), parent=merged_node) - - for nodes in PT.get_children_from_predicates(ref_node, data_query, ancestors=True): - path = '/'.join([PT.get_name(node) for node in nodes]) - # #Rebuild structure if any - sub_ref = ref_node - merged_parent = merged_node - for node in nodes[:-1]: - sub_ref = PT.get_child_from_name(sub_ref, PT.get_name(node)) - merged_parent = PT.update_child(merged_parent, PT.get_name(sub_ref), PT.get_label(sub_ref), PT.get_value(sub_ref)) - if PT.get_label(nodes[-1]) == 'IndexArray_t': - # Recombine (1,N) or (3,N) array - keys = [f'{path}_{idim}' for idim in range(3)] - to_combine = [merged_data[key] for key in keys if key in merged_data] - combined = np.empty((len(to_combine), to_combine[0].size), to_combine[0].dtype, order='F') - for i, array in enumerate(to_combine): - combined[i,:] = array - PT.new_IndexArray(PT.get_name(nodes[-1]), combined, merged_parent) - else: - PT.new_DataArray(PT.get_name(nodes[-1]), merged_data[path], parent=merged_parent) - - additional_types = ['GridLocation_t', 'FamilyName_t', 'Descriptor_t', - 'GridConnectivityType_t', 'GridConnectivityProperty_t'] - additional_names = [] - for type in additional_types: - for sub_node in PT.iter_children_from_label(ref_node, type): - PT.add_child(merged_node, sub_node) - for name in additional_names: - for sub_node in PT.iter_children_from_name(ref_node, name): - PT.add_child(merged_node, sub_node) - - MT.newDistribution({'Index' : merged_pl_distri}, merged_node) - - return merged_node - -def _merge_ngon(all_mbm, tree, comm): - """ - Internal function used by _merge_zones to create the merged NGonNode - """ - - zone_paths = PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t') - zone_to_id = {path : i for i, path in enumerate(zone_paths)} - - # Create working data - for zone_path, dom_id in zone_to_id.items(): - ngon_node = sids.Zone.NGonNode(PT.get_node_from_path(tree, zone_path)) - pe_bck = PT.get_child_from_name(ngon_node, 'ParentElements')[1] - pe = pe_bck.copy() - # If NGon are first, then PE indexes cell, we must shift : PDM expect cell starting at 1 - if sids.Element.Range(ngon_node)[0] == 1: - np_utils.shift_nonzeros(pe, -sids.Element.Size(ngon_node)) - PT.new_DataArray('UpdatedPE', pe, parent=ngon_node) - PT.new_DataArray('PEDomain', dom_id * np.ones_like(pe_bck), parent=ngon_node) - - # First, we need to update the PE node to include cells of opposite zone - query = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] \ - and PT.get_child_from_name(n, '__maia_merge__') is not None - - for zone_path_send in zone_paths: - base_n = zone_path_send.split('/')[0] - dom_id_send = zone_to_id[zone_path_send] - zone_send = PT.get_node_from_path(tree, zone_path_send) - ngon_send = sids.Zone.NGonNode(zone_send) - face_distri_send = MT.getDistribution(ngon_send, 'Element')[1] - pe_send = PT.get_child_from_name(ngon_send, 'UpdatedPE')[1] - dist_data_send = {'PE' : pe_send[:,0]} - - gcs = PT.get_nodes_from_predicate(zone_send, query, depth=2) - all_pls = [PT.get_child_from_name(gc, 'PointList')[1][0] for gc in gcs] - part_data = EP.block_to_part(dist_data_send, face_distri_send, all_pls, comm) - for i, gc in enumerate(gcs): - - pld = PT.get_child_from_name(gc, 'PointListDonor')[1][0] - - #This is the left cell of the join face present in PL. Send it to opposite zone - part_data_gc = {key : [data[i]] for key, data in part_data.items()} - part_data_gc['FaceId'] = [pld] - - # Get send data on the opposite zone and update PE - zone_path = PT.GridConnectivity.ZoneDonorPath(gc, base_n) - zone = PT.get_node_from_path(tree, zone_path) - ngon_node = sids.Zone.NGonNode(zone) - face_distri = MT.getDistribution(ngon_node, 'Element')[1] - dist_data = EP.part_to_block(part_data_gc, face_distri, [pld], comm) - - pe = PT.get_child_from_name(ngon_node, 'UpdatedPE')[1] - pe_dom = PT.get_child_from_name(ngon_node, 'PEDomain')[1] - local_faces = dist_data['FaceId'] - face_distri[0] - 1 - assert np.max(pe[local_faces, 1], initial=0) == 0 #Initial = trick to admit empty array - pe[local_faces, 1] = dist_data['PE'] - pe_dom[local_faces, 1] = dom_id_send - - #PE are ready, collect data - ec_l = [] - ec_stride_l = [] - pe_l = [] - pe_stride_l = [] - pe_dom_l = [] - for zone_path in zone_paths: - ngon_node = sids.Zone.NGonNode(PT.get_node_from_path(tree, zone_path)) - eso = PT.get_child_from_name(ngon_node, 'ElementStartOffset')[1] - pe = PT.get_child_from_name(ngon_node, 'UpdatedPE')[1] - pe_dom = PT.get_child_from_name(ngon_node, 'PEDomain')[1] - - ec_l.append(PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1]) - ec_stride_l.append(np_utils.safe_int_cast(np.diff(eso), np.int32)) - - #We have to detect and remove bnd faces from PE to use PDM stride - bnd_faces = np.where(pe == 0)[0] - stride = 2*np.ones(pe.shape[0], dtype=np.int32) - stride[bnd_faces] = 1 - pe_stride_l.append(stride) - #Also remove 0 from pe and pe_domain - pe_l.append(np.delete(pe.reshape(-1), 2*bnd_faces+1)) - pe_dom_l.append(np_utils.safe_int_cast(np.delete(pe_dom.reshape(-1), 2*bnd_faces+1), np.int32)) - - # Now merge and update - merged_ec_stri, merged_ec = all_mbm['Face'].merge_and_update(all_mbm['Vertex'], ec_l, ec_stride_l) - merged_pe_stri, merged_pe = all_mbm['Face'].merge_and_update(all_mbm['Cell'], pe_l, pe_stride_l, pe_dom_l) - merged_distri_face = all_mbm['Face'].get_merged_distri() - - # Reshift ESO to make it global - eso_loc = np_utils.sizes_to_indices(merged_ec_stri, pdm_dtype) - ec_distri = par_utils.gather_and_shift(eso_loc[-1], comm) - eso = eso_loc + ec_distri[comm.Get_rank()] - - #Post treat PE : we need to reintroduce 0 on boundary faces (TODO : could avoid tmp array ?) - bnd_faces = np.where(merged_pe_stri == 1)[0] - merged_pe_idx = np_utils.sizes_to_indices(merged_pe_stri) - merged_pe_full = np.insert(merged_pe, merged_pe_idx[bnd_faces]+1, 0) - assert (merged_pe_full.size == 2*merged_pe_stri.size) - pe = np.empty((merged_pe_stri.size, 2), order='F', dtype=merged_pe.dtype) - pe[:,0] = merged_pe_full[0::2] - pe[:,1] = merged_pe_full[1::2] - np_utils.shift_nonzeros(pe, merged_distri_face[-1]) - - # Finally : create ngon node - merged_ngon = PT.new_NGonElements(erange=[1, merged_distri_face[-1]], eso=eso, ec=merged_ec, pe=pe) - MT.newDistribution({'Element' : par_utils.full_to_partial_distribution(merged_distri_face, comm), - 'ElementConnectivity' : par_utils.full_to_partial_distribution(ec_distri, comm)}, - merged_ngon) - return merged_ngon - diff --git a/maia/algo/dist/merge_ids.py b/maia/algo/dist/merge_ids.py deleted file mode 100644 index 4b3a3df0..00000000 --- a/maia/algo/dist/merge_ids.py +++ /dev/null @@ -1,68 +0,0 @@ -import numpy as np - -from maia.utils import np_utils, par_utils -from maia.transfer import protocols as EP - - -def remove_distributed_ids(distri, ids, comm): - """ - Delete specified ids from global numbering and renumber the - ids from 1 to n_elts - n_removed_elts. - Return an old_id_to_new_id indirection of size dn_elts - with values -1 at deleted positions. - """ - PTB = EP.PartToBlock(distri, [ids], comm) - dist_ids = PTB.getBlockGnumCopy() - dn_elts = distri[1] - distri[0] - - n_rmvd_local = len(dist_ids) - n_rmvd_offset = par_utils.gather_and_shift(n_rmvd_local, comm) - - old_to_new = -1*np.ones(dn_elts, dtype=ids.dtype) - not_ids_local = np_utils.others_mask(old_to_new, dist_ids-distri[0]-1) - old_to_new[not_ids_local] = np.arange(dn_elts - n_rmvd_local) + distri[0] - n_rmvd_offset[comm.Get_rank()] + 1 - - return old_to_new - -def merge_distributed_ids(distri, ids, targets, comm, sign_rmvd=False): - """ - Map some distributed elements (ids) to others (targets) and shift all the numbering, - in a distributed way. - ids and targets must be of same size and are distributed arrays. - Elements should not appear both in ids and targets arrays. - Return an old_to_new array for all the elements in the distribution. - If sign_rmvd is True, input ids maps to -target instead of target - in old_to_new array. - """ - - # Move data to procs holding ids, merging multiple elements - PTB = EP.PartToBlock(distri, [ids], comm) - dist_ids = PTB.getBlockGnumCopy() - - _, dist_targets = PTB.exchange_field([targets]) - - # Count the number of elements to be deleted (that is the number of elts received, after merge) - n_rmvd_local = len(dist_ids) - n_rmvd_offset = par_utils.gather_and_shift(n_rmvd_local, comm) - - # Initial old_to_new - total_ids_size = distri[1]-distri[0] - old_to_new = np.empty(total_ids_size, dtype=ids.dtype) - ids_local = dist_ids - distri[0] - 1 - not_ids_local = np_utils.others_mask(old_to_new, ids_local) - unchanged_ids_size = total_ids_size - ids_local.size - old_to_new[not_ids_local] = np.arange(unchanged_ids_size) + distri[0] + 1 - - # Shift global : for each index, substract the number of targets removed by preceding ranks - old_to_new -= n_rmvd_offset[comm.Get_rank()] - - # Now we need to update old_to_new for ids to indicate new indices of targets. - # Since the new index of target can be on another proc, we do a (fake) BTP to - # get the data using target numbering - dist_data2 = {'OldToNew' : old_to_new} - part_data2 = EP.block_to_part(dist_data2, distri, [dist_targets], comm) - - marker = -1 if sign_rmvd else 1 - old_to_new[ids_local] = marker * part_data2['OldToNew'][0] - - return old_to_new diff --git a/maia/algo/dist/merge_jn.py b/maia/algo/dist/merge_jn.py deleted file mode 100644 index 1d36ea99..00000000 --- a/maia/algo/dist/merge_jn.py +++ /dev/null @@ -1,319 +0,0 @@ -from mpi4py import MPI -import numpy as np -import itertools - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.utils import par_utils - -from maia.algo.dist import remove_element as RME -from maia.algo.dist import matching_jns_tools as MJT -from maia.algo.dist.merge_ids import merge_distributed_ids -from maia.algo.dist import vertex_list as VL - -from maia.transfer import protocols as EP - -def _update_ngon(ngon, ref_faces, del_faces, vtx_distri_ini, old_to_new_vtx, comm): - """ - Update ngon node after face and vertex merging, ie - - update ParentElements to combinate faces - - remove faces from EC, PE and ESO and update distribution info - - update ElementConnectivity using vertex old_to_new order - """ - face_distri = PT.get_value(MT.getDistribution(ngon, 'Element')) - pe = PT.get_node_from_path(ngon, 'ParentElements')[1] - - - #TODO This method asserts that PE is CGNS compliant ie left_parent != 0 for bnd elements - assert not np.any(pe[:,0] == 0) - - # A/ Exchange parent cells before removing : - # 1. Get the left cell of the faces to delete - dist_data = {'PE' : pe[:,0]} - part_data = EP.block_to_part(dist_data, face_distri, [del_faces], comm) - # 2. Put it in the right cell of the faces to keep - #TODO : exchange of ref_faces could be avoided using get gnum copy - part_data['FaceId'] = [ref_faces] - dist_data = EP.part_to_block(part_data, face_distri, [ref_faces], comm) - - local_faces = dist_data['FaceId'] - face_distri[0] - 1 - assert np.max(pe[local_faces, 1], initial=0) == 0 #Initial = trick to admit empty array - pe[local_faces, 1] = dist_data['PE'] - - # B/ Update EC, PE and ESO removing some faces - part_data = [del_faces] - dist_data = EP.part_to_block(part_data, face_distri, [del_faces], comm) - local_faces = dist_data - face_distri[0] - 1 - RME.remove_ngons(ngon, local_faces, comm) - - # C/ Update vertex ids in EC - ngon_ec_n = PT.get_child_from_name(ngon, 'ElementConnectivity') - part_data = EP.block_to_part(old_to_new_vtx, vtx_distri_ini, [PT.get_value(ngon_ec_n)], comm) - assert len(ngon_ec_n[1]) == len(part_data[0]) - PT.set_value(ngon_ec_n, part_data[0]) - -def _update_nface(nface, face_distri_ini, old_to_new_face, n_rmvd_face, comm): - """ - Update nface node after face merging, ie - - update ElementConnectivity using face old_to_new order - - Shift ElementRange (to substract nb of removed faces) if NFace is after NGon - If input array old_to_new_face is signed (ie is negative for face ids that will be removed), - then the orientation of nface connectivity is preserved - """ - - #Update list of faces - nface_ec_n = PT.get_child_from_name(nface, 'ElementConnectivity') - part_data = EP.block_to_part(old_to_new_face, face_distri_ini, [np.abs(nface_ec_n[1])], comm) - assert len(nface_ec_n[1]) == len(part_data[0]) - #Get sign of nface_ec to preserve orientation - PT.set_value(nface_ec_n, np.sign(nface_ec_n[1]) * part_data[0]) - - #Update ElementRange - er = PT.Element.Range(nface) - if er[0] > 1: - er -= n_rmvd_face - -def _update_subset(node, pl_new, data_query, comm): - """ - Update a PointList and all the data - """ - part_data = {} - dist_data = {} - for data_nodes in PT.iter_children_from_predicates(node, data_query, ancestors=True): - path = "/".join([PT.get_name(n) for n in data_nodes]) - data_n = data_nodes[-1] - if data_n[1].ndim == 1: - part_data[path] = [data_n[1]] - else: - assert data_n[1].ndim == 2 and data_n[1].shape[0] == 1 - part_data[path] = [data_n[1][0]] - - #Add PL, needed for next blocktoblock - pl_identifier = r'@\PointList/@' # just a string that is unlikely to clash - part_data[pl_identifier] = [pl_new] - - PTB = EP.PartToBlock(None, [pl_new], comm) - PTB.PartToBlock_Exchange(dist_data, part_data) - - d_pl_new = PTB.getBlockGnumCopy() - - new_distri_full = par_utils.gather_and_shift(len(d_pl_new), comm, pdm_dtype) - #Result is badly distributed, we can do a BlockToBlock to have a uniform distribution - ideal_distri = par_utils.uniform_distribution(new_distri_full[-1], comm) - dist_data_ideal = EP.block_to_block(dist_data, new_distri_full, ideal_distri, comm) - - #Update distribution and size - MT.newDistribution({'Index' : ideal_distri}, node) - - #Update PointList and data - PT.update_child(node, 'PointList', 'IndexArray_t', dist_data_ideal.pop(pl_identifier).reshape(1,-1, order='F')) - #Update data - for data_nodes in PT.iter_children_from_predicates(node, data_query, ancestors=True): - path = "/".join([PT.get_name(n) for n in data_nodes]) - if PT.get_label(data_nodes[-1]) == 'IndexArray_t': - PT.set_value(data_nodes[-1], dist_data_ideal[path].reshape(1,-1, order='F')) - elif PT.get_label(data_nodes[-1]) == 'DataArray_t': - PT.set_value(data_nodes[-1], dist_data_ideal[path]) - -def _update_cgns_subsets(zone, location, entity_distri, old_to_new_face, base_name, comm): - """ - Treated for now : - BC, BCDataset (With or without PL), FlowSol, DiscreteData, ZoneSubRegion, JN - - Careful! PointList/PointListDonor arrays of joins present in the zone are updated, but opposite joins - are not informed of this modification. This has to be done after the function. - """ - - # Prepare iterators - matches_loc = lambda n : PT.Subset.GridLocation(n) == location - is_bcds_with_pl = lambda n: PT.get_label(n) == 'BCDataSet_t'and PT.get_child_from_name(n, 'PointList') is not None - is_bcds_without_pl = lambda n: PT.get_label(n) == 'BCDataSet_t'and PT.get_child_from_name(n, 'PointList') is None - - is_sol = lambda n: PT.get_label(n) in ['FlowSolution_t', 'DiscreteData_t'] and matches_loc(n) - is_bc = lambda n: PT.get_label(n) == 'BC_t' and matches_loc(n) - is_bcds = lambda n: is_bcds_with_pl(n) and matches_loc(n) - is_zsr = lambda n: PT.get_label(n) == 'ZoneSubRegion_t' and matches_loc(n) - is_jn = lambda n: PT.get_label(n) == 'GridConnectivity_t' and matches_loc(n) - - sol_list = PT.getChildrenFromPredicate(zone, is_sol) - bc_list = PT.getChildrenFromPredicates(zone, ['ZoneBC_t', is_bc]) - bcds_list = PT.getChildrenFromPredicates(zone, ['ZoneBC_t', 'BC_t', is_bcds]) - zsr_list = PT.getChildrenFromPredicate(zone, is_zsr) - jn_list = PT.getChildrenFromPredicates(zone, ['ZoneGridConnectivity_t', is_jn]) - i_jn_list = [jn for jn in jn_list if PT.GridConnectivity.ZoneDonorPath(jn, base_name) == base_name + '/'+ PT.get_name(zone)] - - #Loop in same order using to get apply pl using generic func - all_nodes_and_queries = [ - ( sol_list , ['DataArray_t'] ), - ( bc_list , [is_bcds_without_pl, 'BCData_t', 'DataArray_t'] ), - ( bcds_list, ['BCData_t', 'DataArray_t'] ), - ( zsr_list , ['DataArray_t'] ), - ( jn_list , ['PointListDonor'] ), - ] - all_nodes = itertools.chain.from_iterable([elem[0] for elem in all_nodes_and_queries]) - - #Trick to add a PL to each subregion to be able to use same algo - for zsr in zsr_list: - if PT.Subset.ZSRExtent(zsr, zone) != PT.get_name(zsr): - PT.add_child(zsr, PT.get_node_from_path(zone, PT.Subset.ZSRExtent(zsr, zone) + '/PointList')) - - #Get new index for every PL at once - all_pl_list = [PT.get_child_from_name(fs, 'PointList')[1][0] for fs in all_nodes] - part_data_pl = EP.block_to_part(old_to_new_face, entity_distri, all_pl_list, comm) - - part_offset = 0 - for node_list, data_query in all_nodes_and_queries: - for node in node_list: - _update_subset(node, part_data_pl[part_offset], data_query, comm) - part_offset += 1 - - #For internal jn only, we must update PointListDonor with new face id. Non internal jn reorder the array, - # but do not apply old_to_new transformation. - # Note that we will lost symmetry PL/PLD for internal jn, we need a rule to update it afterward - all_pld = [PT.get_child_from_name(jn, 'PointListDonor') for jn in i_jn_list] - updated_pld = EP.block_to_part(old_to_new_face, entity_distri, [pld[1][0] for pld in all_pld], comm) - for i, pld in enumerate(all_pld): - PT.set_value(pld, updated_pld[i].reshape((1,-1), order='F')) - - #Cleanup after trick - for zsr in zsr_list: - if PT.Subset.ZSRExtent(zsr, zone) != PT.get_name(zsr): - PT.rm_children_from_name(zsr, 'PointList') - - -# TODO move to sids module, doc, unit test -#(take the one of _shift_cgns_subsets, and for _shift_cgns_subsets, make a trivial test) -def all_nodes_with_point_list(zone, pl_location): - has_pl = lambda n: PT.get_child_from_name(n, 'PointList') is not None \ - and PT.Subset.GridLocation(n) == pl_location - return itertools.chain( - PT.getChildrenFromPredicate(zone, has_pl) , #FlowSolution_t, ZoneSubRegion_t, ... - PT.getChildrenFromPredicates(zone, ['ZoneBC_t', has_pl]) , #BC_t - #For this one we must exclude BC since predicate is also tested on root (and should not be ?) - PT.getChildrenFromPredicates(zone, ['ZoneBC_t', 'BC_t', lambda n : has_pl(n) and PT.get_label(n) != 'BC_t']) , #BCDataSet_t - PT.getChildrenFromPredicates(zone, ['ZoneGridConnectivity_t', has_pl]), #GridConnectivity_t - ) - -def _shift_cgns_subsets(zone, location, shift_value): - """ - Shift all the PointList of the requested location with the given value - PointList are seached in every node below zone, + in BC_t, BCDataSet_t, - GridConnectivity_t - """ - for node in all_nodes_with_point_list(zone,location): - PT.get_child_from_name(node, 'PointList')[1][0] += shift_value - -def _update_vtx_data(zone, vtx_to_remove, comm): - """ - Remove the vertices in data array supported by allVertex (currently - managed : GridCoordinates, FlowSolution, DiscreteData) - and update vertex distribution info - """ - vtx_distri_ini = PT.get_value(MT.getDistribution(zone, 'Vertex')) - pdm_distrib = par_utils.partial_to_full_distribution(vtx_distri_ini, comm) - - PTB = EP.PartToBlock(vtx_distri_ini, [vtx_to_remove], comm) - local_vtx_to_rmv = PTB.getBlockGnumCopy() - vtx_distri_ini[0] - 1 - - #Update all vertex entities - for coord_n in PT.iter_children_from_predicates(zone, ['GridCoordinates_t', 'DataArray_t']): - PT.set_value(coord_n, np.delete(coord_n[1], local_vtx_to_rmv)) - - is_all_vtx_sol = lambda n: PT.get_label(n) in ['FlowSolution_t', 'DiscreteData_t'] \ - and PT.Subset.GridLocation(n) == 'Vertex' and PT.get_node_from_path(n, 'PointList') is None - - for node in PT.iter_children_from_predicate(zone, is_all_vtx_sol): - for data_n in PT.iter_children_from_label(node, 'DataArray_t'): - PT.set_value(data_n, np.delete(data_n[1], local_vtx_to_rmv)) - - # Update vertex distribution - i_rank, n_rank = comm.Get_rank(), comm.Get_size() - n_rmvd = len(local_vtx_to_rmv) - n_rmvd_offset = par_utils.gather_and_shift(n_rmvd, comm, pdm_dtype) - vtx_distri = vtx_distri_ini - [n_rmvd_offset[i_rank], n_rmvd_offset[i_rank+1], n_rmvd_offset[n_rank]] - MT.newDistribution({'Vertex' : vtx_distri}, zone) - zone[1][0][0] = vtx_distri[2] - - - -def merge_intrazone_jn(dist_tree, jn_pathes, comm): - """ - """ - base_n, zone_n, zgc_n, gc_n = jn_pathes[0].split('/') - gc = PT.get_node_from_path(dist_tree, jn_pathes[0]) - assert PT.Subset.GridLocation(gc) == 'FaceCenter' - zone = PT.get_node_from_path(dist_tree, base_n + '/' + zone_n) - ngon = [elem for elem in PT.iter_children_from_label(zone, 'Elements_t') if elem[1][0] == 22][0] - nface_l = [elem for elem in PT.iter_children_from_label(zone, 'Elements_t') if elem[1][0] == 23] - nface = nface_l[0] if len(nface_l) == 1 else None - - MJT.add_joins_donor_name(dist_tree, comm) - - ref_faces = PT.get_node_from_path(dist_tree, jn_pathes[0]+'/PointList')[1][0] - face_to_remove = PT.get_node_from_path(dist_tree, jn_pathes[0]+'/PointListDonor')[1][0] - #Create pl and pl_d from vertex, needed to know which vertex will be deleted - ref_vtx, vtx_to_remove, _ = VL.generate_jn_vertex_list(dist_tree, jn_pathes[0], comm) - # In some cases, we can have some vertices shared between PlVtx and PldVtx (eg. when a vertex - # belongs to more than 2 gcs, and some of them has been deleted. - # We just ignore thoses vertices by removing them from the arrays - vtx_is_shared = ref_vtx == vtx_to_remove - ref_vtx = ref_vtx[~vtx_is_shared] - vtx_to_remove = vtx_to_remove[~vtx_is_shared] - assert np.intersect1d(ref_faces, face_to_remove).size == 0 - assert np.intersect1d(ref_vtx, vtx_to_remove).size == 0 - - #Get initial distributions - face_distri_ini = PT.get_value(MT.getDistribution(ngon, 'Element')).copy() - vtx_distri_ini = PT.get_value(MT.getDistribution(zone, 'Vertex')) - - old_to_new_face = merge_distributed_ids(face_distri_ini, face_to_remove, ref_faces, comm, True) - old_to_new_vtx = merge_distributed_ids(vtx_distri_ini, vtx_to_remove, ref_vtx, comm) - old_to_new_face_unsg = np.abs(old_to_new_face) - - n_rmvd_face = comm.allreduce(len(face_to_remove), op=MPI.SUM) - - _update_ngon(ngon, ref_faces, face_to_remove, vtx_distri_ini, old_to_new_vtx, comm) - if nface: - _update_nface(nface, face_distri_ini, old_to_new_face, n_rmvd_face, comm) - - _update_vtx_data(zone, vtx_to_remove, comm) - - _update_cgns_subsets(zone, 'FaceCenter', face_distri_ini, old_to_new_face_unsg, base_n, comm) - _update_cgns_subsets(zone, 'Vertex', vtx_distri_ini, old_to_new_vtx, base_n, comm) - #Shift all CellCenter PL by the number of removed faces - if PT.Element.Range(ngon)[0] == 1: - _shift_cgns_subsets(zone, 'CellCenter', -n_rmvd_face) - - # Since PointList/PointList donor of JN have changed, we must change opposite join as well - # Carefull : for intra zone jn, we may have a clash of actualized pl/pld. We use pathes to break it - jn_to_opp = {} - current_zone_path = base_n + '/' + zone_n - all_gcs_query = ['CGNSBase_t', 'Zone_t', 'ZoneGridConnectivity_t', 'GridConnectivity_t'] - for zgc, gc in PT.iter_children_from_predicates(zone, all_gcs_query[2:], ancestors=True): - jn_to_opp[base_n + '/' + zone_n + '/' + zgc[0] + '/' + gc[0]] = \ - (np.copy(PT.get_child_from_name(gc, 'PointList')[1]), np.copy(PT.get_child_from_name(gc, 'PointListDonor')[1])) - for o_base, o_zone, o_zgc, o_gc in PT.iter_children_from_predicates(dist_tree, all_gcs_query, ancestors=True): - gc_path = '/'.join([PT.get_name(node) for node in [o_base, o_zone, o_zgc, o_gc]]) - gc_path_opp = MJT.get_jn_donor_path(dist_tree, gc_path) - try: - pl_opp, pld_opp = jn_to_opp[gc_path_opp] - # Skip one internal jn over two - if PT.get_name(o_base) + '/' + PT.get_name(o_zone) == current_zone_path and gc_path_opp >= gc_path: - pass - else: - PT.set_value(PT.get_child_from_name(o_gc, 'PointList'), pld_opp) - PT.set_value(PT.get_child_from_name(o_gc, 'PointListDonor'), pl_opp) - #Since we modify the PointList of this join, we must check that no data is related to it - assert PT.get_child_from_label(o_gc, 'DataArray_t') is None, \ - "Can not reorder a GridConnectivity PointList to which data is related" - for zsr in PT.iter_children_from_label(o_zone, 'ZoneSubRegion_t'): - assert PT.Subset.ZSRExtent(zsr, o_zone) != PT.get_name(o_zgc) + '/' + PT.get_name(o_gc), \ - "Can not reorder a GridConnectivity PointList to which data is related" - except KeyError: - pass - - #Cleanup - PT.rm_node_from_path(dist_tree, jn_pathes[0]) - PT.rm_node_from_path(dist_tree, jn_pathes[1]) diff --git a/maia/algo/dist/mesh_adaptation.py b/maia/algo/dist/mesh_adaptation.py deleted file mode 100644 index eada10f2..00000000 --- a/maia/algo/dist/mesh_adaptation.py +++ /dev/null @@ -1,336 +0,0 @@ -import time -import subprocess -from pathlib import Path - -import maia -import maia.pytree as PT -import maia.utils.logging as mlog - -from maia.io.meshb_converter import cgns_to_meshb, meshb_to_cgns, get_tree_info -from maia.algo.dist.matching_jns_tools import add_joins_donor_name, get_matching_jns -from maia.algo.dist.adaptation_utils import convert_vtx_gcs_as_face_bcs,\ - deplace_periodic_patch,\ - retrieve_initial_domain,\ - rm_feflo_added_elt - - -# TMP directory -tmp_repo = Path('TMP_adapt_repo') - -# INPUT files -in_file_meshb = tmp_repo / 'mesh.mesh' -in_file_solb = tmp_repo / 'metric.sol' -in_file_fldb = tmp_repo / 'field.sol' -in_files = {'mesh': in_file_meshb, - 'sol' : in_file_solb , - 'fld' : in_file_fldb } - -mesh_back_file = tmp_repo / 'mesh_back.mesh' - -# OUTPUT files -out_file_meshb = tmp_repo / 'mesh.o.mesh' -out_file_solb = tmp_repo / 'mesh.o.sol' -out_file_fldb = tmp_repo / 'field.itp.sol' -out_files = {'mesh': out_file_meshb, - 'sol' : out_file_solb , - 'fld' : out_file_fldb } - -# Feflo files arguments -feflo_args = { 'isotrop' : f"-iso ".split(), - 'from_fld' : f"-sol {in_file_solb}".split(), - 'from_hess': f"-met {in_file_solb}".split() -} - - -def unpack_metric(dist_tree, metric_paths): - """ - Unpacks the `metric` argument from `mesh_adapt` function. - Assert no invalid path or argument is given and that paths leads to one or six fields. - """ - if metric_paths is None: - return list() - - zones = PT.get_all_Zone_t(dist_tree) - assert len(zones) == 1 - zone = zones[0] - - # > Get metrics nodes described by path - if isinstance(metric_paths, str): - container_name, fld_name = metric_paths.split('/') - metric_nodes = [PT.get_node_from_path(zone, f"{container_name}/{fld_name}{suffix}") \ - for suffix in ['', 'XX', 'XY', 'XZ', 'YY', 'YZ', 'ZZ']] - metric_nodes = [node for node in metric_nodes if node is not None] # Above list contains found node or None - elif isinstance(metric_paths, list): - assert len(metric_paths)==6, f"metric argument must be a str path or a list of 6 paths" - metric_nodes = list() - for path in metric_paths: - metric_nodes.append(PT.get_node_from_path(zone, path)) - else: - raise ValueError(f"Incorrect metric type (expected list or str).") - - # > Assert that metric is one or six fields - if len(metric_nodes)==0: - raise ValueError(f"Metric path \"{metric_paths}\" is invalid.") - if len(metric_nodes)==7: - raise ValueError(f"Metric path \"{metric_paths}\" simultaneously leads to scalar *and* tensor fields") - if len(metric_nodes)!=1 and len(metric_nodes)!=6: - raise ValueError(f"Metric path \"{metric_paths}\" leads to {len(metric_nodes)} nodes (1 or 6 expected).") - - return metric_nodes - - -def _adapt_mesh_with_feflo(dist_tree, metric, comm, container_names, constraints, feflo_opts): - tmp_repo.mkdir(exist_ok=True) - - # > Get metric nodes - metric_nodes = unpack_metric(dist_tree, metric) - metric_type = {0: 'isotrop', 1: 'from_fld', 6: 'from_hess'}[len(metric_nodes)] - - # > Get tree structure and names - tree_info = get_tree_info(dist_tree, container_names) - tree_info = comm.bcast(tree_info, root=0) - input_base = PT.get_child_from_label(dist_tree, 'CGNSBase_t') - input_zone = PT.get_child_from_label(input_base, 'Zone_t') - - - # > CGNS to meshb conversion - if comm.Get_rank()==0: - constraint_tags = cgns_to_meshb(dist_tree, in_files, metric_nodes, container_names, constraints) - - # Adapt with feflo - feflo_itp_args = f'-itp {in_file_fldb}'.split() if len(container_names)!=0 else [] - feflo_command = ['feflo.a', '-in', str(in_files['mesh'])] + feflo_args[metric_type] + feflo_itp_args + feflo_opts.split() - if len(constraint_tags['FaceCenter'])!=0: - feflo_command = feflo_command + ['-adap-surf-ids'] + [','.join(constraint_tags['FaceCenter'])]#[str(tag) for tag in constraint_tags['FaceCenter']] - if len(constraint_tags['EdgeCenter'])!=0: - feflo_command = feflo_command + ['-adap-line-ids'] + [','.join(constraint_tags['EdgeCenter'])]#[str(tag) for tag in constraint_tags['EdgeCenter']] - feflo_command = ' '.join(feflo_command) # Split + join to remove useless spaces - - mlog.info(f"Start mesh adaptation using Feflo...") - start = time.time() - - subprocess.run(feflo_command, shell=True) - - end = time.time() - mlog.info(f"Feflo mesh adaptation completed ({end-start:.2f} s)") - - - # > Get adapted dist_tree - adapted_dist_tree = meshb_to_cgns(out_files, tree_info, comm) - - # > Set names and copy base data - adapted_base = PT.get_child_from_label(adapted_dist_tree, 'CGNSBase_t') - adapted_zone = PT.get_child_from_label(adapted_base, 'Zone_t') - PT.set_name(adapted_base, PT.get_name(input_base)) - PT.set_name(adapted_zone, PT.get_name(input_zone)) - - to_copy = lambda n: PT.get_label(n) in ['Family_t'] - for node in PT.get_nodes_from_predicate(input_base, to_copy): - PT.add_child(adapted_base, node) - - # > Copy BC data - to_copy = lambda n: PT.get_label(n) in ['FamilyName_t', 'AdditionalFamilyName_t'] - for bc_path in PT.predicates_to_paths(adapted_zone, 'ZoneBC_t/BC_t'): - adapted_bc = PT.get_node_from_path(adapted_zone, bc_path) - input_bc = PT.get_node_from_path(input_zone, bc_path) - if input_bc is not None: - PT.set_value(adapted_bc, PT.get_value(input_bc)) - for node in PT.get_nodes_from_predicate(input_bc, to_copy): - PT.add_child(adapted_bc, node) - - return adapted_dist_tree - -def _adapt_mesh_with_feflo_perio(dist_tree, metric, comm, container_names, feflo_opts): - ''' - Assume that : - - Only one Element node for each dimension - - Mesh is full TRI_3 and TETRA_4 - - GridConnectivities have no common vertices - - - 1ere extension de domaine: - 1/ tag des cellules qui ont un vtx dans la gc - 2/ création de la surface de contrainte: - - création des faces à partir de la connectivité des cellules tagguées sans les vertex de la gc - - suppression des faces qui sont déjà définies dans les BCs, ou les possibles doublons de face créées par 2 cellules - 3/ duplication des éléments de la surface contrainte - 4/ déplacement des vertex du patch - 5/ merge des surfaces de la GC - - 2eme extension de domaine: - 1/ get bc cellules (issu du mesh adapté) - 2/ duplication des éléments de la gc - 3/ déplacement des vertex du patch - 4/ merge des surfaces de contrainte - 5/ retrouver les ridges (et corners) disparus à l'étape 1 - - - Issues: - - would ngon be faster ? - - gérer la périodisation des champs (rotation) - - [x] vector - - [ ] tensor -> useful for metric - - [?] some variables (angle in torus case) - - cas du cone -> arete sur l'axe bien gérée ? - - TODO: - - manage back mesh - [!] beware of BC ordering that have been changed - - manage n_range of cells - - tout adapter pour le 2d ? - ''' - tree = PT.deep_copy(dist_tree) # Do not modify input tree - - start = time.time() - # > Get periodic infos - add_joins_donor_name(tree, comm) # Add missing joins donor names - perio_jns_pairs = get_matching_jns(tree, lambda n : PT.GridConnectivity.isperiodic(n)) - jn_pairs_and_values = dict() - for pair in perio_jns_pairs: - gc_nodes = (PT.get_node_from_path(tree, gc_path) for gc_path in pair) - jn_pairs_and_values[pair] = [PT.GridConnectivity.periodic_values(gc) for gc in gc_nodes] - - convert_vtx_gcs_as_face_bcs(tree, comm) - - mlog.info(f"[Periodic adaptation] Step #1: Duplicating periodic patch...") - - new_vtx_num, bcs_to_constrain, bcs_to_retrieve = deplace_periodic_patch(tree, perio_jns_pairs, comm) - - end = time.time() - # maia.io.dist_tree_to_file(adapted_dist_tree, 'OUTPUT/extended_domain.cgns', comm) - mlog.info(f"[Periodic adaptation] Step #1 completed: ({end-start:.2f} s)") - - - mlog.info(f"[Periodic adaptation] Step #2: First adaptation constraining periodic patches boundaries...") - maia.algo.dist.redistribute_tree(tree, 'gather.0', comm) - tree = _adapt_mesh_with_feflo(tree, metric, comm, container_names, bcs_to_constrain, feflo_opts) - - - mlog.info(f"[Periodic adaptation] #3: Removing initial domain...") - start = time.time() - - retrieve_initial_domain(tree, jn_pairs_and_values, new_vtx_num, bcs_to_retrieve, comm) - - end = time.time() - # maia.io.dist_tree_to_file(adapted_dist_tree, 'OUTPUT/initial_domain.cgns', comm) - mlog.info(f"[Periodic adaptation] Step #3 completed: ({end-start:.2f} s)") - - - mlog.info(f"[Periodic adaptation] #4: Perform last adaptation constraining periodicities...") - gc_constraints = [PT.path_tail(gc_path) for pair in perio_jns_pairs for gc_path in pair] - maia.algo.dist.redistribute_tree(tree, 'gather.0', comm) - tree = _adapt_mesh_with_feflo(tree, metric, comm, container_names, gc_constraints, feflo_opts) - - - # > Retrieve periodicities + cleaning file - PT.rm_nodes_from_name_and_label(tree, 'PERIODIC', 'Family_t', depth=2) - PT.rm_nodes_from_name_and_label(tree, 'GCS', 'Family_t', depth=2) - for zone in PT.get_all_Zone_t(tree): - PT.rm_children_from_name_and_label(zone, 'maia_topo','FlowSolution_t') - PT.rm_nodes_from_name_and_label(zone, 'tetra_4_periodic*','BC_t', depth=2) - - # > Set family name in BCs for connect_match - for i_jn, jn_pair in enumerate(perio_jns_pairs): - for i_gc, gc_path in enumerate(jn_pair): - bc_path = PT.update_path_elt(gc_path, 2, lambda n: 'ZoneBC') # gc has been stored as a BC - bc_n = PT.get_node_from_path(tree, bc_path) - PT.update_child(bc_n, 'FamilyName', 'FamilyName_t', f'BC_TO_CONVERT_{i_jn}_{i_gc}') - - for i_jn, jn_values in enumerate(jn_pairs_and_values.values()): - maia.algo.dist.connect_1to1_families(tree, (f'BC_TO_CONVERT_{i_jn}_0', f'BC_TO_CONVERT_{i_jn}_1'), comm, - periodic=jn_values[0].asdict(True), location='Vertex') - - # > Remove '_0' in the created GCs names - for jn_pair in perio_jns_pairs: - for gc_path in jn_pair: - gc_n = PT.get_node_from_path(tree, gc_path+'_0') - PT.set_name(gc_n, PT.path_tail(gc_path)) - gcd_name_n = PT.get_child_from_name(gc_n, 'GridConnectivityDonorName') - PT.set_value(gcd_name_n, PT.get_value(gcd_name_n)[:-2]) - PT.rm_children_from_label(gc_n, 'FamilyName_t') - - rm_feflo_added_elt(PT.get_node_from_label(tree, 'Zone_t'), comm) - - return tree - - - -def adapt_mesh_with_feflo(dist_tree, metric, comm, container_names=[], constraints=None, periodic=False, feflo_opts=""): - """Run a mesh adaptation step using *Feflo.a* software. - - Important: - - Feflo.a is an Inria software which must be installed by you and exposed in your ``$PATH``. - - This API is experimental. It may change in the future. - - Input tree must be unstructured and have an element connectivity. - Boundary conditions other than Vertex located are managed. - - Adapted mesh is returned as an independant distributed tree. - - **Setting the metric** - - Metric choice is available through the ``metric`` argument, which can take the following values: - - - *None* : isotropic adaptation is performed - - *str* : path (starting a Zone_t level) to a scalar or tensorial vertex located field: - - - If the path leads to a scalar field (e.g. FlowSolution/Pressure), a metric is computed by - Feflo from this field. - - If the path leads to a tensorial field (e.g. FlowSolution/HessMach), we collect its 6 component (named - after CGNS tensor conventions) and pass it to - Feflo as a user-defined metric. - - :: - - FlowSolution FlowSolution_t - ├───GridLocation GridLocation_t "Vertex" - ├───Pressure DataArray_t R8 (100,) - ├───HessMachXX DataArray_t R8 (100,) - ├───HessMachYY DataArray_t R8 (100,) - ├───... - └───HessMachYZ DataArray_t R8 (100,) - - - *list of 6 str* : each string must be a path to a vertex located field representing one component - of the user-defined metric tensor (expected order is ``XX, XY, XZ, YY, YZ, ZZ``) - - **Periodic mesh adaptation** - - Periodic mesh adaptation is available by activating the ``periodic`` argument. Information from - periodic 1to1 GridConnectivity_t nodes in dist_tree will be used to perform mesh adaptation. - - Args: - dist_tree (CGNSTree) : Distributed tree to be adapted. Only U-Elements - single zone trees are managed. - metric (str or list) : Path(s) to metric fields (see above) - comm (MPIComm) : MPI communicator - container_names(list of str) : Name of some Vertex located FlowSolution to project on the adapted mesh - constraints (list of str) : BC names of entities that must not be adapted (default to None) - periodic (boolean) : perform periodic mesh adaptation - feflo_opts (str) : Additional arguments passed to Feflo - Returns: - CGNSTree: Adapted mesh (distributed) - - Warning: - Although this function interface is parallel, keep in mind that Feflo.a is a sequential tool. - Input tree is thus internally gathered to a single process, which can cause memory issues on large cases. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #adapt_with_feflo@start - :end-before: #adapt_with_feflo@end - :dedent: 2 - """ - - if periodic: - adapted_dist_tree = _adapt_mesh_with_feflo_perio(dist_tree, metric, comm, container_names, feflo_opts) - else: - # > Gathering dist_tree on proc 0 - maia.algo.dist.redistribute_tree(dist_tree, 'gather.0', comm) # Modifie le dist_tree - - adapted_dist_tree = _adapt_mesh_with_feflo(dist_tree, metric, comm, container_names, constraints, feflo_opts) - PT.rm_nodes_from_name_and_label(adapted_dist_tree, 'maia_topo','FlowSolution_t') - - # > Recover original dist_tree - maia.algo.dist.redistribute_tree(dist_tree, 'uniform', comm) - - return adapted_dist_tree diff --git a/maia/algo/dist/mixed_to_std_elements.py b/maia/algo/dist/mixed_to_std_elements.py deleted file mode 100644 index 89b6be17..00000000 --- a/maia/algo/dist/mixed_to_std_elements.py +++ /dev/null @@ -1,266 +0,0 @@ -import mpi4py.MPI as mpi - -import numpy as np - -import maia -from maia import pytree as PT -from maia.pytree import maia as MT -from maia.transfer import protocols as MTP -from maia.utils import par_utils as MUPar -from maia.utils.ndarray import np_utils - -import maia.pytree.sids.elements_utils as MPSEU - -def collect_pl_nodes(root, filter_loc=None): - """ - Search and collect all the pointList nodes found in subsets found - under root - If filter_loc list is not None, select only the PointList nodes of given - GridLocation. - Remark : if the subset is defined by a PointRange, we replace the PointRange node - to the equivalent PointList node and collect the new PointList node - """ - pointlist_nodes = [] - for node in PT.get_all_subsets(root,filter_loc): - pl_n = PT.get_child_from_name(node, 'PointList') - pr_n = PT.get_child_from_name(node, 'PointRange') - if pl_n is not None: - pointlist_nodes.append(pl_n) - elif pr_n is not None and PT.get_value(pr_n).shape[0] == 1: - pr = PT.get_value(pr_n) - distrib = PT.get_value(PT.maia.getDistribution(node, 'Index')) - pl = np_utils.single_dim_pr_to_pl(pr, distrib) - new_pl_n = PT.new_node(name='PointList', value=pl, label='IndexArray_t', parent=node) - PT.rm_nodes_from_label(node,'IndexRange_t') - pointlist_nodes.append(new_pl_n) - return pointlist_nodes - -def convert_mixed_to_elements(dist_tree, comm): - """ - Transform a mixed connectivity into an element based connectivity. - - Tree is modified in place : mixed elements are removed from the zones - and the PointList are updated. - - Args: - dist_tree (CGNSTree): Tree with connectivity described by mixed elements - comm (`MPIComm`) : MPI communicator - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #convert_mixed_to_elements@start - :end-before: #convert_mixed_to_elements@end - :dedent: 2 - """ - rank = comm.Get_rank() - size = comm.Get_size() - - for zone in PT.get_all_Zone_t(dist_tree): - elem_types = {} # For each element type: dict id of mixed node -> number of elts - ec_per_elem_type_loc = {} - - # 1/ Create local element connectivity for each element type found in each mixed node - # and deduce the local number of each element type - for elem_pos,element in enumerate(PT.Zone.get_ordered_elements(zone)): - assert PT.Element.CGNSName(element) == 'MIXED' - elem_er = PT.Element.Range(element) - elem_ec = PT.get_child_from_name(element,'ElementConnectivity')[1] - elem_eso = PT.get_child_from_name(element,'ElementStartOffset')[1] - elem_eso_loc = elem_eso[:-1]-elem_eso[0] - elem_types_tab = elem_ec[elem_eso_loc] - elem_types_loc, nb_elems_per_types_loc = np.unique(elem_types_tab,return_counts=True) - for e, elem_type in enumerate(elem_types_loc): - if elem_type not in elem_types.keys(): - elem_types[elem_type] = {} - elem_types[elem_type][elem_pos] = nb_elems_per_types_loc[e] - nb_nodes_per_elem = MPSEU.element_number_of_nodes(elem_type) - ec_per_type = np.empty(nb_nodes_per_elem*nb_elems_per_types_loc[e],dtype=elem_ec.dtype) - # Retrive start idx of mixed elements having this type - indices = np.intersect1d(np.where(elem_ec==elem_type),elem_eso_loc, assume_unique=True) - for n in range(nb_nodes_per_elem): - ec_per_type[n::nb_nodes_per_elem] = elem_ec[indices+n+1] - try: - ec_per_elem_type_loc[elem_type].append(ec_per_type) - except KeyError: - ec_per_elem_type_loc[elem_type] = [ec_per_type] - - # 2/ Find all element types described in the mesh and the number of each - elem_types_all = comm.allgather(elem_types) - all_types = {} # For each type : total number of elts appearing in zone - for proc_dict in elem_types_all: - for kd,vd in proc_dict.items(): - if kd not in all_types: - all_types[kd] = 0 - all_types[kd] += sum(vd.values()) - all_types = dict(sorted(all_types.items())) - - - # 3/ To take into account Paraview limitation, elements are sorted by - # decreased dimensions : 3D->2D->1D->0D - # Without this limitation, replace the following lines by: - # `key_types = np.array(list(all_types.keys()),dtype=np.int32)` - key_types = sorted(all_types.keys(), key=MPSEU.element_dim, reverse=True) - - - # 4/ Create old to new element numbering (to update PointList/PointRange) - # and old to new cell numbering (to update CellCenter FlowSolution) - cell_dim = max([MPSEU.element_dim(k) for k in key_types]) - ln_to_gn_element_list = [] - ln_to_gn_cell_list = [] - old_to_new_element_numbering_list = [] - old_to_new_cell_numbering_list = [] - nb_elem_prev_element_t_nodes = 0 - for elem_pos,element in enumerate(PT.Zone.get_ordered_elements(zone)): - elem_ec = PT.get_child_from_name(element, 'ElementConnectivity')[1] - elem_eso = PT.get_child_from_name(element, 'ElementStartOffset')[1] - elem_distrib = MT.getDistribution(element, 'Element')[1] - nb_elem_loc = elem_distrib[1]-elem_distrib[0] - nb_cell_loc = 0 - for et in elem_types: - if MPSEU.element_dim(et) == cell_dim: - try: - nb_cell_loc += elem_types[et][elem_pos] - except KeyError: - pass - old_to_new_element_numbering = np.zeros(nb_elem_loc,dtype=elem_eso.dtype) - old_to_new_cell_numbering = np.zeros(nb_cell_loc,dtype=maia.npy_pdm_gnum_dtype) - ln_to_gn_element = np.arange(nb_elem_loc,dtype=maia.npy_pdm_gnum_dtype) + 1\ - + elem_distrib[0] + nb_elem_prev_element_t_nodes - ln_to_gn_cell = np.arange(nb_cell_loc,dtype=maia.npy_pdm_gnum_dtype) + 1 - nb_elem_prev_element_t_nodes += elem_distrib[2] - all_elem_previous_types = 0 - all_cell_previous_types = 0 - - elem_ec_type_pos = elem_ec[elem_eso[:-1]-elem_eso[0]] # Type of each element - all_elem_pos = {} - all_non_cell_pos = [] - for elem_type in key_types: - all_elem_pos[elem_type] = np.where(elem_ec_type_pos==elem_type)[0] - if MPSEU.element_dim(elem_type) != cell_dim: - all_non_cell_pos += list(all_elem_pos[elem_type]) - all_non_cell_pos = sorted(all_non_cell_pos) - all_cell_pos = {} - for elem_type in key_types: - if MPSEU.element_dim(elem_type) == cell_dim: - all_cell_pos[elem_type] = all_elem_pos[elem_type] - np.searchsorted(all_non_cell_pos,all_elem_pos[elem_type]) - for elem_type in key_types: - nb_elems_per_type = all_types[elem_type] - indices_elem = all_elem_pos[elem_type] - old_to_new_element_numbering[indices_elem] = np.arange(len(indices_elem),dtype=elem_eso.dtype) + 1 - is_cell = MPSEU.element_dim(elem_type) == cell_dim - if is_cell: - indices_cell = all_cell_pos[elem_type] - old_to_new_cell_numbering[indices_cell] = np.arange(len(indices_cell),dtype=elem_eso.dtype) + 1 - old_to_new_cell_numbering[indices_cell] += all_cell_previous_types - # Add total elements of others (previous) type - old_to_new_element_numbering[indices_elem] += all_elem_previous_types - # Add number of elements on previous mixed nodes for this rank - # Add number of cells on previous mixed nodes for this rank - for p in range(elem_pos): - try: - old_to_new_element_numbering[indices_elem] += elem_types[elem_type][p] - except KeyError: - continue - if is_cell: - for r in range(size): - try: - ln_to_gn_cell += elem_types_all[r][elem_type][p] - old_to_new_cell_numbering[indices_cell] += elem_types_all[r][elem_type][p] - except KeyError: - continue - # Add number of element on previous procs for this mixed node - # Add number of cells on previous procs for this mixed node - for r in range(rank): - try: - nb_elem_per_type_per_pos_per_rank = elem_types_all[r][elem_type][elem_pos] - except KeyError: - continue - old_to_new_element_numbering[indices_elem] += nb_elem_per_type_per_pos_per_rank - if is_cell: - ln_to_gn_cell += nb_elem_per_type_per_pos_per_rank - old_to_new_cell_numbering[indices_cell] += nb_elem_per_type_per_pos_per_rank - all_elem_previous_types += all_types[elem_type] - if is_cell: - all_cell_previous_types += all_types[elem_type] - - old_to_new_element_numbering_list.append(old_to_new_element_numbering) - old_to_new_cell_numbering_list.append(old_to_new_cell_numbering) - ln_to_gn_element_list.append(ln_to_gn_element) - ln_to_gn_cell_list.append(ln_to_gn_cell) - - - # 5/ Delete mixed nodes and add standard elements nodes - PT.rm_nodes_from_label(zone,'Elements_t') - beg_erange = 1 - for elem_type in key_types: - nb_elems_per_type = all_types[elem_type] - part_data_ec = [] - part_stride_ec = [] - ln_to_gn_list = [] - label = MPSEU.element_name(elem_type) - nb_nodes_per_elem = MPSEU.element_number_of_nodes(elem_type) - erange = [beg_erange, beg_erange+nb_elems_per_type-1] - - if elem_type in ec_per_elem_type_loc: - for p,pos in enumerate(elem_types[elem_type]): - nb_nodes_loc = elem_types[elem_type][pos] - part_data_ec.append(ec_per_elem_type_loc[elem_type][p]) - stride_ec = (nb_nodes_per_elem)*np.ones(nb_nodes_loc,dtype = np.int32) - part_stride_ec.append(stride_ec) - ln_to_gn = np.arange(nb_nodes_loc,dtype=elem_distrib.dtype) + 1 - - offset = 0 - for r,elem_types_rank in enumerate(elem_types_all): - if elem_type in elem_types_rank: - # Add number of elts on previous mixed nodes for all ranks - offset += sum([nb for other_pos, nb in elem_types_rank[elem_type].items() if other_pos < pos]) - # Add number of elts on this mixed node, but only for previous ranks - if r < rank and pos in elem_types_rank[elem_type]: - offset += elem_types_rank[elem_type][pos] - ln_to_gn_list.append(ln_to_gn + offset) - - elem_distrib = MUPar.uniform_distribution(nb_elems_per_type,comm) - ptb_elem = MTP.PartToBlock(elem_distrib,ln_to_gn_list,comm) - _, econn = ptb_elem.exchange_field(part_data_ec,part_stride_ec) - - beg_erange += nb_elems_per_type - elem_n = PT.new_Elements(label.capitalize(),label,erange=erange,econn=econn,parent=zone) - PT.maia.newDistribution({'Element' : elem_distrib}, parent=elem_n) - - - # 6/ Update all PointList with GridLocation != Vertex - filter_loc = ['EdgeCenter','FaceCenter','CellCenter'] - pl_list = collect_pl_nodes(zone,filter_loc) - - ln_to_gn_pl_list = [maia.utils.as_pdm_gnum(PT.get_value(pl)[0]) for pl in pl_list] - - old_to_new_pl_list = MTP.part_to_part(old_to_new_element_numbering_list, ln_to_gn_element_list, ln_to_gn_pl_list, comm) - - for pl_node, new_pl in zip(pl_list, old_to_new_pl_list): - PT.set_value(pl_node, np.array(new_pl).reshape((1,-1), order='F')) - - - # 7/ Update all FlowSolution with GridLocation == CellCenter - - # 7a. Redistribute old_to_new_cell_numbering to be coherent with - # cells distribution - cells_distrib = MT.getDistribution(zone, 'Cell')[1] - ptb_cell = MTP.PartToBlock(cells_distrib,ln_to_gn_cell_list,comm) - - _, dist_old_to_new_cell_numbering = ptb_cell.exchange_field(old_to_new_cell_numbering_list) - - # 7b. Reorder FlowSolution DataArray - ptb_fs = MTP.PartToBlock(cells_distrib,[dist_old_to_new_cell_numbering],comm) - - old_fs_data_dict = {} - for fs in PT.get_children_from_predicate(zone, lambda n: PT.get_label(n) in ['FlowSolution_t', 'DiscreteData_t']): - if PT.get_value(PT.get_child_from_name(fs,'GridLocation')) == 'CellCenter' \ - and PT.get_child_from_name(fs,'PointList') is None: - for data in PT.get_children_from_label(fs,'DataArray_t'): - old_fs_data_dict[PT.get_name(fs)+"/"+PT.get_name(data)] = [PT.get_value(data)] - - for fs_data_name, old_fs_data in old_fs_data_dict.items(): - _, new_fs_data = ptb_fs.exchange_field(old_fs_data) - data_node = PT.get_node_from_path(zone,fs_data_name) - PT.set_value(data_node, new_fs_data) - diff --git a/maia/algo/dist/ngon_from_std_elements.py b/maia/algo/dist/ngon_from_std_elements.py deleted file mode 100644 index 57a11806..00000000 --- a/maia/algo/dist/ngon_from_std_elements.py +++ /dev/null @@ -1,250 +0,0 @@ -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia -from maia.utils import np_utils, par_utils, layouts - -from maia.algo.dist import remove_element as RME -from maia.algo.dist import matching_jns_tools as MJT -from maia.factory.partitioning.split_U.cgns_to_pdm_dmesh import cgns_dist_zone_to_pdm_dmesh_nodal - -import Pypdm.Pypdm as PDM - -def raise_if_possible_overflow(n_elt, n_rank): - max_int = 2**31 - 1 - if n_elt > n_rank * max_int: - req = n_elt // max_int + 1 - msg = f"Size of data seems to be too large regarding the number of MPI ranks. "\ - f"Please try with at least {req} processes." - raise OverflowError(msg) - -def _create_pe_global(flat_array, shift_value): - pe = np.empty((flat_array.size//2, 2), dtype=flat_array.dtype, order='F') - layouts.pdm_face_cell_to_pe_cgns(flat_array, pe) - np_utils.shift_nonzeros(pe, shift_value) - return pe - -def predict_face_vtx_size(zone, dim): - n_vtx_mult = {'BAR_2' : 2, 'TRI_3': 3, 'QUAD_4': 4, - 'TETRA_4': 12, 'PYRA_5': 16, 'PENTA_6': 18, 'HEXA_8': 24} - face_vtx_size = 0 - elt_predicate = lambda n : PT.get_label(n) == 'Elements_t' and PT.Element.Dimension(n) >= dim - 1 - for elt in PT.iter_children_from_predicate(zone, elt_predicate): - try: - face_vtx_size += PT.Element.Size(elt) * n_vtx_mult[PT.Element.CGNSName(elt)] - except KeyError: - pass - return face_vtx_size - -def cgns_zone_to_pdm_dmesh_nodal(zone, comm, extract_dim): - elts_per_dim = PT.Zone.get_ordered_elements_per_dim(zone) - assert len(elts_per_dim[0]) == 0, "NODE elements are not supported in STD->NGON conversion" - dmn = cgns_dist_zone_to_pdm_dmesh_nodal(zone, comm, needs_vertex=False) - dmn.generate_distribution() - return dmn - -def pdm_dmesh_to_cgns_zone(result_dmesh, zone, comm, extract_dim): - """ - """ - i_rank = comm.Get_rank() - - #Manage BCs : shift PL values to reach refer bnd elements - if extract_dim == 2: - group_idx, pdm_group = result_dmesh.dmesh_bound_get(PDM._PDM_BOUND_TYPE_EDGE) - keep_location = 'EdgeCenter' - skip_location = ['FaceCenter', 'CellCenter'] - elif extract_dim == 3: - group_idx, pdm_group = result_dmesh.dmesh_bound_get(PDM._PDM_BOUND_TYPE_FACE) - keep_location = 'FaceCenter' - skip_location = ['EdgeCenter', 'CellCenter'] - converted_bc = lambda n : PT.get_label(n) == 'BC_t' and PT.Subset.GridLocation(n) == keep_location - unconverted_bc = lambda n : PT.get_label(n) == 'BC_t' and PT.Subset.GridLocation(n) in skip_location - if pdm_group is not None: - group = np.copy(pdm_group) - for i_bc, bc in enumerate(PT.iter_children_from_predicates(zone, ['ZoneBC_t', converted_bc])): - PT.rm_nodes_from_name(bc, 'PointRange') - PT.rm_nodes_from_name(bc, 'PointList') - start, end = group_idx[i_bc], group_idx[i_bc+1] - PT.new_IndexArray(value=group[start:end].reshape((1,-1), order='F'), parent=bc) - - # Remove unconverted BCs - for zbc in PT.get_nodes_from_label(zone, 'ZoneBC_t'): - PT.rm_nodes_from_predicate(zbc, unconverted_bc) - - # Remove std elements - PT.rm_children_from_label(zone, 'Elements_t') - - # Create polyedric elements - if extract_dim == 3: - dface_cell_idx, dface_cell = result_dmesh.dmesh_connectivity_get(PDM._PDM_CONNECTIVITY_TYPE_FACE_CELL) - dface_vtx_idx, dface_vtx = result_dmesh.dmesh_connectivity_get(PDM._PDM_CONNECTIVITY_TYPE_FACE_VTX) - dcell_face_idx, dcell_face = result_dmesh.dmesh_connectivity_get(PDM._PDM_CONNECTIVITY_TYPE_CELL_FACE) - distrib_face = result_dmesh.dmesh_distrib_get(PDM._PDM_MESH_ENTITY_FACE) - distrib_cell = result_dmesh.dmesh_distrib_get(PDM._PDM_MESH_ENTITY_CELL) - - n_face = distrib_face[-1] - n_cell = distrib_cell[-1] - - distrib_face_vtx = par_utils.gather_and_shift(dface_vtx_idx[-1], comm, distrib_face.dtype) - distrib_cell_face = par_utils.gather_and_shift(dcell_face_idx[-1], comm, distrib_cell.dtype) - - # Create NGON - ngon_er = np.array([1, n_face], dtype=dface_vtx.dtype) - ngon_pe = _create_pe_global(dface_cell, n_face) - ngon_eso = dface_vtx_idx + distrib_face_vtx[i_rank] - ngon_eso = np_utils.safe_int_cast(ngon_eso, ngon_er.dtype) - - ngon_n = PT.new_NGonElements(erange=ngon_er, eso=ngon_eso, ec=dface_vtx, pe=ngon_pe, parent=zone) - MT.newDistribution({'Element' : par_utils.full_to_partial_distribution(distrib_face, comm), - 'ElementConnectivity' : par_utils.full_to_partial_distribution(distrib_face_vtx, comm)}, - ngon_n) - - # Create NFACE - nface_er = np.array([1, n_cell], dtype=dcell_face.dtype) + n_face - nface_eso = dcell_face_idx + distrib_cell_face[i_rank] - nface_eso = np_utils.safe_int_cast(nface_eso, nface_er.dtype) - - nfac_n = PT.new_NFaceElements(erange=nface_er, eso=nface_eso, ec=dcell_face, parent=zone) - MT.newDistribution({'Element' : par_utils.full_to_partial_distribution(distrib_cell, comm), - 'ElementConnectivity' : par_utils.full_to_partial_distribution(distrib_cell_face, comm)}, - nfac_n) - - elif extract_dim == 2: - dedge_face_idx, dedge_face = result_dmesh.dmesh_connectivity_get(PDM._PDM_CONNECTIVITY_TYPE_EDGE_FACE) - dedge_vtx_idx, dedge_vtx = result_dmesh.dmesh_connectivity_get(PDM._PDM_CONNECTIVITY_TYPE_EDGE_VTX) - dface_edge_idx, dface_edge = result_dmesh.dmesh_connectivity_get(PDM._PDM_CONNECTIVITY_TYPE_FACE_EDGE) - distrib_edge = result_dmesh.dmesh_distrib_get(PDM._PDM_MESH_ENTITY_EDGE) - distrib_face = result_dmesh.dmesh_distrib_get(PDM._PDM_MESH_ENTITY_FACE) - - n_edge = distrib_edge[-1] - n_face = distrib_face[-1] - - distrib_face_vtx = par_utils.gather_and_shift(dface_edge_idx[-1], comm, distrib_face.dtype) # Same as distri_face_edge - - edge_er = np.array([1, n_edge], dtype=dedge_vtx.dtype) - edge_pe = _create_pe_global(dedge_face, n_edge) - - bar_n = PT.new_Elements('EdgeElements', 'BAR_2', erange=edge_er, econn=dedge_vtx, parent=zone) - PT.new_DataArray('ParentElements', edge_pe, parent=bar_n) - MT.newDistribution({'Element' : par_utils.full_to_partial_distribution(distrib_edge, comm)}, - bar_n) - - # Create NGON (combine face_edge + edge_vtx) - ngon_er = np.array([1, n_face], dtype=dface_edge.dtype) + n_edge - ngon_ec = PDM.compute_dfacevtx_from_face_and_edge(comm, distrib_face, distrib_edge, dface_edge_idx, dface_edge, dedge_vtx) - ngon_eso = dface_edge_idx + distrib_face_vtx[i_rank] - ngon_eso = np_utils.safe_int_cast(ngon_eso, ngon_er.dtype) - - ngon_n = PT.new_NGonElements(erange=ngon_er, eso=ngon_eso, ec=ngon_ec, parent=zone) - MT.newDistribution({'Element' : par_utils.full_to_partial_distribution(distrib_face, comm), - 'ElementConnectivity' : par_utils.full_to_partial_distribution(distrib_face_vtx, comm)}, - ngon_n) - - - # > Remove internal holder state - PT.rm_nodes_from_name(zone, ':CGNS#DMeshNodal#Bnd*') - - -def generate_ngon_from_std_elements(dist_tree, comm): - """ - Transform an element based connectivity into a polyedric (NGon based) - connectivity. - - Tree is modified in place : standard element are removed from the zones - and Pointlist (under the BC_t nodes) are updated. - - Requirement : the ``Element_t`` nodes appearing in the distributed zones - must be ordered according to their dimension (either increasing or - decreasing). - - This function also works on 2d meshes. - - Args: - dist_tree (CGNSTree): Tree with connectivity described by standard elements - comm (`MPIComm`) : MPI communicator - """ - MJT.add_joins_donor_name(dist_tree, comm) - # Convert gcs into bc, so they will be converted by the function - for zgc in PT.iter_nodes_from_label(dist_tree, 'ZoneGridConnectivity_t'): - PT.set_label(zgc, 'ZoneBC_t') - PT.new_node('__maia::isZGC', parent=zgc) - for gc in PT.iter_children_from_label(zgc, 'GridConnectivity_t'): - PT.set_label(gc, 'BC_t') - - for base in PT.iter_all_CGNSBase_t(dist_tree): - extract_dim = PT.get_value(base)[0] - zones_u = [zone for zone in PT.iter_all_Zone_t(base) if PT.Zone.Type(zone) == "Unstructured"] - - for zone in zones_u: #Raise if overflow is probable - face_vtx_size = predict_face_vtx_size(zone, extract_dim) - raise_if_possible_overflow(face_vtx_size*np.dtype(maia.npy_pdm_gnum_dtype).itemsize, comm.Get_size()) - - dmn_to_dm = PDM.DMeshNodalToDMesh(len(zones_u), comm) - for i_zone, zone in enumerate(zones_u): - dmn = cgns_zone_to_pdm_dmesh_nodal(zone, comm, extract_dim) - dmn_to_dm.add_dmesh_nodal(i_zone, dmn) - - # PDM_DMESH_NODAL_TO_DMESH_TRANSFORM_TO_FACE - face = "EDGE" if extract_dim == 2 else "FACE" - dmn_to_dm.compute(eval(f"PDM._PDM_DMESH_NODAL_TO_DMESH_TRANSFORM_TO_{face}"), - eval(f"PDM._PDM_DMESH_NODAL_TO_DMESH_TRANSLATE_GROUP_TO_{face}")) - - for i_zone, zone in enumerate(zones_u): - result_dmesh = dmn_to_dm.get_dmesh(i_zone) - pdm_dmesh_to_cgns_zone(result_dmesh, zone, comm, extract_dim) - - # > Generate correctly zone_grid_connectivity - for zbc in PT.iter_nodes_from_label(dist_tree, 'ZoneBC_t'): - if PT.get_child_from_name(zbc, '__maia::isZGC'): - PT.set_label(zbc, 'ZoneGridConnectivity_t') - PT.rm_children_from_name(zbc, '__maia::isZGC') - for bc in PT.iter_children_from_label(zbc, 'BC_t'): - PT.set_label(bc, 'GridConnectivity_t') - MJT.copy_donor_subset(dist_tree) - -def convert_elements_to_ngon(dist_tree, comm, stable_sort=False): - """ - Transform an element based connectivity into a polyedric (NGon based) - connectivity. - - Tree is modified in place : standard element are removed from the zones - and the PointList are updated. If ``stable_sort`` is True, face based PointList - keep their original values. - - Requirement : the ``Element_t`` nodes appearing in the distributed zones - must be ordered according to their dimension (either increasing or - decreasing). Tree made of *Mixed* elements are supported - (:func:`convert_mixed_to_elements` is called under the hood). - - Args: - dist_tree (CGNSTree): Tree with connectivity described by standard elements - comm (`MPIComm`) : MPI communicator - stable_sort (bool, optional) : If True, 2D elements described in the - elements section keep their original id. Defaults to False. - - Note that ``stable_sort`` is an experimental feature that brings the additional - constraints: - - - 2D meshes are not supported; - - 2D sections must have lower ElementRange than 3D sections. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #convert_elements_to_ngon@start - :end-before: #convert_elements_to_ngon@end - :dedent: 2 - """ - # If tree has MIXED elements, first convert Mixed -> Elts - is_mixed = lambda n: PT.get_label(n) == 'Elements_t' and PT.Element.CGNSName(n) == 'MIXED' - has_mixed = PT.get_node_from_predicates(dist_tree, ['CGNSBase_t', 'Zone_t', is_mixed]) is not None - if has_mixed: - maia.algo.dist.convert_mixed_to_elements(dist_tree, comm) - - if stable_sort: - from .elements_to_ngons import elements_to_ngons - elements_to_ngons(dist_tree, comm) - else: - generate_ngon_from_std_elements(dist_tree, comm) - diff --git a/maia/algo/dist/ngon_tools.py b/maia/algo/dist/ngon_tools.py deleted file mode 100644 index 2dd9bc6c..00000000 --- a/maia/algo/dist/ngon_tools.py +++ /dev/null @@ -1,102 +0,0 @@ -import numpy as np - -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.algo import indexing -from maia.utils import par_utils, np_utils - -def PDM_dfacecell_to_dcellface(comm, face_distri, cell_distri, face_cell): - _face_distri = np_utils.safe_int_cast(face_distri, PDM.npy_pdm_gnum_dtype) - _cell_distri = np_utils.safe_int_cast(cell_distri, PDM.npy_pdm_gnum_dtype) - _dface_cell = np_utils.safe_int_cast(face_cell, PDM.npy_pdm_gnum_dtype) - _cell_face_idx, _cell_face = PDM.dfacecell_to_dcellface(comm, _face_distri, _cell_distri, _dface_cell) - cell_face_idx = np_utils.safe_int_cast(_cell_face_idx, face_cell.dtype) - cell_face = np_utils.safe_int_cast(_cell_face, face_cell.dtype) - return cell_face_idx, cell_face - -def PDM_dcellface_to_dfacecell(comm, face_distri, cell_distri, cell_face_idx, cell_face): - _face_distri = np_utils.safe_int_cast(face_distri, PDM.npy_pdm_gnum_dtype) - _cell_distri = np_utils.safe_int_cast(cell_distri, PDM.npy_pdm_gnum_dtype) - _cell_face_idx = np_utils.safe_int_cast(cell_face_idx, np.int32) - _cell_face = np_utils.safe_int_cast(cell_face, PDM.npy_pdm_gnum_dtype) - _face_cell = PDM.dcellface_to_dfacecell(comm, _face_distri, _cell_distri, _cell_face_idx, _cell_face) - return np_utils.safe_int_cast(_face_cell, cell_face.dtype) - - -def pe_to_nface(zone, comm, remove_PE=False): - """Create a NFace node from a NGon node with ParentElements. - - NGon range is supposed to start at 1. - Input tree is modified inplace. - - Args: - zone (CGNSTree): Distributed zone - comm (MPIComm) : MPI communicator - remove_PE (bool, optional): If True, remove the ParentElements node. - Defaults to False. - """ - ngon_node = PT.Zone.NGonNode(zone) - nface_distri = MT.getDistribution(zone, 'Cell')[1] - ngon_distri = MT.getDistribution(ngon_node, 'Element')[1] - face_distri = par_utils.partial_to_full_distribution(ngon_distri, comm) - cell_distri = par_utils.partial_to_full_distribution(nface_distri, comm) - assert PT.Element.Range(ngon_node)[0] == 1 - local_pe = indexing.get_ngon_pe_local(ngon_node).reshape(-1, order='C') - - cell_face_idx, cell_face = PDM_dfacecell_to_dcellface(comm, face_distri, cell_distri, local_pe) - cell_face_range = np.array([1, PT.Zone.n_cell(zone)], zone[1].dtype) + PT.Zone.n_face(zone) - nface_ec_distr_f = par_utils.gather_and_shift(cell_face_idx[-1], comm) - nface_ec_distri = par_utils.full_to_partial_distribution(nface_ec_distr_f, comm) - eso = cell_face_idx + nface_ec_distri[0] - - nface = PT.new_NFaceElements(erange=cell_face_range, eso=eso, ec=cell_face, parent=zone) - MT.newDistribution({"Element" : nface_distri, "ElementConnectivity" : nface_ec_distri}, nface) - - if remove_PE: - PT.rm_children_from_name(ngon_node, "ParentElements") - -def nface_to_pe(zone, comm, remove_NFace=False): - """Create a ParentElements node in the NGon node from a NFace node. - - Input tree is modified inplace. - - Args: - zone (CGNSTree): Distributed zone - comm (MPIComm) : MPI communicator - remove_NFace (bool, optional): If True, remove the NFace node. - Defaults to False. - """ - ngon_node = PT.Zone.NGonNode(zone) - nface_node = PT.Zone.NFaceNode(zone) - ngon_distri = MT.getDistribution(ngon_node , 'Element')[1] - nface_distri = MT.getDistribution(nface_node, 'Element')[1] - nface_distri_c = MT.getDistribution(nface_node, 'ElementConnectivity')[1] - - face_distri = par_utils.partial_to_full_distribution(ngon_distri, comm) - cell_distri = par_utils.partial_to_full_distribution(nface_distri, comm) - cell_face_idx = PT.get_child_from_name(nface_node, "ElementStartOffset")[1] - cell_face = PT.get_child_from_name(nface_node, "ElementConnectivity")[1] - - # If NFace are before NGon, then face ids must be shifted - if PT.Element.Range(ngon_node)[0] == 1: - _cell_face = cell_face - else: - _cell_face_sign = np.sign(cell_face) - _cell_face = np.abs(cell_face) - PT.Element.Size(nface_node) - _cell_face = _cell_face * _cell_face_sign - _cell_face_idx = cell_face_idx - nface_distri_c[0] #Go to local idx - - face_cell = PDM_dcellface_to_dfacecell(comm, face_distri, cell_distri, _cell_face_idx, _cell_face) - np_utils.shift_nonzeros(face_cell, PT.Element.Range(nface_node)[0]-1) # Refer to NFace global ids - - pe = np.empty((ngon_distri[1] - ngon_distri[0], 2), dtype=face_cell.dtype, order='F') - pe[:,0] = face_cell[0::2] - pe[:,1] = face_cell[1::2] - - PT.new_DataArray('ParentElements', pe, parent=ngon_node) - if remove_NFace: - PT.rm_child(zone, nface_node) - diff --git a/maia/algo/dist/ngons_to_elements.py b/maia/algo/dist/ngons_to_elements.py deleted file mode 100644 index 8d23c5d8..00000000 --- a/maia/algo/dist/ngons_to_elements.py +++ /dev/null @@ -1,27 +0,0 @@ -from cmaia import dist_algo as cdist_algo -from maia.algo.apply_function_to_nodes import apply_to_zones - -from maia.utils import require_cpp20 - -@require_cpp20 -def ngons_to_elements(t,comm): - """ - Transform a polyedric (NGon) based connectivity into a standard nodal - connectivity. - - Tree is modified in place : Polyedric element are removed from the zones - and Pointlist (under the BC_t nodes) are updated. - - Requirement : polygonal elements are supposed to describe only standard - elements (ie tris, quads, tets, pyras, prisms and hexas) - - WARNING: this function has not been parallelized yet - - Args: - disttree (CGNSTree): Tree with connectivity described by NGons - comm (`MPIComm`) : MPI communicator - """ - if (comm.Get_size() > 1): - raise RuntimeError("WARNING: this function has not been parallelized yet. Run it on only one process") - apply_to_zones(cdist_algo.convert_zone_to_std_elements, t) - diff --git a/maia/algo/dist/put_boundary_first/__init__.py b/maia/algo/dist/put_boundary_first/__init__.py deleted file mode 100644 index 02dc0122..00000000 --- a/maia/algo/dist/put_boundary_first/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from cmaia import dist_algo as cdist_algo -from maia.algo.apply_function_to_nodes import apply_to_bases,apply_to_zones - -from maia.utils import require_cpp20 - -@require_cpp20 -def put_boundary_first(t, comm): - apply_to_bases(cdist_algo.put_boundary_first, t, comm) - diff --git a/maia/algo/dist/rearrange_element_sections/__init__.py b/maia/algo/dist/rearrange_element_sections/__init__.py deleted file mode 100644 index ec4f02a8..00000000 --- a/maia/algo/dist/rearrange_element_sections/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -from cmaia import dist_algo as cdist_algo -from maia.algo.apply_function_to_nodes import apply_to_bases,apply_to_zones - -from maia.utils import require_cpp20 - -@require_cpp20 -def rearrange_element_sections(dist_tree, comm): - """ - Rearanges Elements_t sections such that for each zone, - sections are ordered in ascending dimensions order - and there is only one section by ElementType. - Sections are renamed based on their ElementType. - - The tree is modified in place. - The Elements_t nodes are guaranteed to be ordered by ascending ElementRange. - - Args: - dist_tree (CGNSTree): Tree with an element-based connectivity - comm (`MPIComm`): MPI communicator - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #rearrange_element_sections@start - :end-before: #rearrange_element_sections@end - :dedent: 2 - """ - apply_to_bases(cdist_algo.rearrange_element_sections, dist_tree, comm) - diff --git a/maia/algo/dist/rearrange_element_sections/rearrange_element_sections.cpp b/maia/algo/dist/rearrange_element_sections/rearrange_element_sections.cpp deleted file mode 100644 index 7378b61a..00000000 --- a/maia/algo/dist/rearrange_element_sections/rearrange_element_sections.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/rearrange_element_sections/rearrange_element_sections.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "maia/utils/parallel/distribution.hpp" -#include "std_e/interval/interval_sequence.hpp" -#include "std_e/parallel/mpi.hpp" -#include "pdm_multi_block_to_part.h" -#include "std_e/data_structure/multi_range.hpp" -#include "maia/pytree/maia/element_sections.hpp" - -using namespace cgns; - -namespace maia { - -template auto -merge_same_type_elt_sections(It first_section, It last_section, MPI_Comm comm) -> tree { - int i_rank = std_e::rank(comm); - int n_rank = std_e::n_rank(comm); - - int n_section = last_section-first_section; - STD_E_ASSERT(n_section); - auto range_first = ElementRange(*first_section); - I4 start = range_first[0]; - I4 finish = range_first[1]; - auto elt_type = element_type(*first_section); - - It cur = first_section+1; - while (cur != last_section) { - auto cur_range = ElementRange(*cur); - I4 cur_start = cur_range[0]; - I4 cur_finish = cur_range[1]; - I4 cur_elt_type = element_type(*first_section); - STD_E_ASSERT(cur_start == finish+1); - STD_E_ASSERT(cur_elt_type == elt_type); - finish = cur_finish; - ++cur; - } - - // multi_block_to_part - std::vector multi_distrib_idx(n_section+1); - multi_distrib_idx[0] = 0; - std::vector> block_distribs_storer(n_section); - std::vector block_distribs(n_section); - for (int i=0; i(section_node); - block_distribs_storer[i] = distribution_from_partial(section_connec_partial_distri,comm); - block_distribs[i] = block_distribs_storer[i].data(); - multi_distrib_idx[i+1] = multi_distrib_idx[i] + section_connec_partial_distri.back(); - } - - const int n_block = n_section; - const int n_part = 1; - - std::vector merged_distri(n_rank+1); - std_e::uniform_distribution(begin(merged_distri),end(merged_distri),0,multi_distrib_idx.back()); - - int n_elts_0 = merged_distri[i_rank+1]-merged_distri[i_rank]; - std::vector ln_to_gn_0(n_elts_0); - std::iota(begin(ln_to_gn_0),end(ln_to_gn_0),merged_distri[i_rank]+1); - std::vector ln_to_gn = {ln_to_gn_0.data()}; - std::vector n_elts = {n_elts_0}; - - PDM_MPI_Comm pdm_comm = PDM_MPI_mpi_2_pdm_mpi_comm(&comm); - PDM_multi_block_to_part_t* mbtp = - PDM_multi_block_to_part_create(multi_distrib_idx.data(), - n_block, - (const PDM_g_num_t**) block_distribs.data(), - (const PDM_g_num_t**) ln_to_gn.data(), - n_elts.data(), - n_part, - pdm_comm); - int stride = cgns::number_of_vertices(elt_type); - int** stride_one = (int ** ) malloc( n_block * sizeof(int *)); - for(int i_block = 0; i_block < n_block; ++i_block){ - stride_one[i_block] = (int * ) malloc( 1 * sizeof(int)); - stride_one[i_block][0] = stride; - } - - std::vector d_connectivity_sections(n_section); - for (int i=0; i(section_node,"ElementConnectivity"); - d_connectivity_sections[i] = section_connec.data(); - } - - I4** parray = nullptr; - PDM_multi_block_to_part_exch2(mbtp, sizeof(I4), PDM_STRIDE_CST_INTERLACED, - stride_one, - (void ** ) d_connectivity_sections.data(), - nullptr, - (void ***) &parray); - - int d_connec_sz = n_elts_0*stride; - std::vector d_connectivity_merge(d_connec_sz); - std::copy_n(parray[0],d_connec_sz,begin(d_connectivity_merge)); - free(parray[0]); - free(parray); - PDM_multi_block_to_part_free(mbtp); - - tree elt_node = new_Elements( - cgns::to_string((ElementType_t)elt_type), - elt_type, - std::move(d_connectivity_merge), - start,finish - ); - - std::vector partial_dist(3); - partial_dist[0] = merged_distri[i_rank]; - partial_dist[1] = merged_distri[i_rank+1]; - partial_dist[2] = merged_distri.back(); - tree dist = new_DataArray("Element",std::move(partial_dist)); - - tree cgns_dist = new_UserDefinedData(":CGNS#Distribution"); - emplace_child(cgns_dist,std::move(dist)); - - emplace_child(elt_node,std::move(cgns_dist)); - - return elt_node; -} - -auto rearrange_element_sections(tree& b, MPI_Comm comm) -> void { - auto zs = get_children_by_label(b,"Zone_t"); - if (zs.size()!=1) { - throw cgns_exception("rearrange_element_sections: only implemented for one zone"); - } - tree& z = zs[0]; - - auto elt_sections = element_sections_ordered_by_range_type_dim(z); - - // Handle NGon case - if (elt_sections.size()>=1) { - auto it_ngon = std::find_if(begin(elt_sections),end(elt_sections),[](const tree& n){ return element_type(n)==NGON_n; }); - if (it_ngon != end(elt_sections)) { // found NGon - if (elt_sections.size()==1) { // only NGon - return; // only 1 NGon : nothing to merge - } else { - if (elt_sections.size()==2) { - auto it_nface = std::find_if(begin(elt_sections),end(elt_sections),[](const tree& n){ return element_type(n)==NFACE_n; }); - if (it_nface == end(elt_sections)) { // did not found NFace - throw cgns_exception("Zone "+name(z)+" has a NGon section, but also a section different from NFace, which is forbidden by CGNS/SIDS"); - } else { - return; // only 1 NGon and 1 NFace : nothing to merge - } - } else { // more sections than Ngon+NFace - throw cgns_exception("Zone "+name(z)+" has a NGon section and a NFace section, but also other element types, which is forbidden by CGNS/SIDS"); - } - } - } - } - - // 0. new ranges - I4 new_offset = 1; - std::vector old_offsets; - std::vector new_offsets; - for (tree& elt_section : elt_sections) { - auto range = ElementRange(elt_section); - - I4 old_offset = range[0]; - old_offsets.push_back(old_offset); - - I4 n_elt = range[1] - range[0] +1; - range[0] = new_offset; - range[1] = new_offset + n_elt -1; - - new_offsets.push_back(new_offset); - - new_offset += n_elt; - } - old_offsets.push_back(new_offset); // since the range span does not change, last new_offset == last old_offset - new_offsets.push_back(0); // value unused, but sizes must be equal for zip_sort - std_e::zip_sort(std::tie(old_offsets,new_offsets)); - - // 1. renumber - // TODO for several zones - // TODO fields - auto bcs = get_nodes_by_matching(z,"ZoneBC_t/BC_t"); - for (tree& bc : bcs) { - if (GridLocation(bc)!="Vertex") { - //auto pl = get_child_value_by_name(bc,"PointList"); - auto pl = get_child_value_by_name(bc,"PointList"); - - for (I4& id : pl) { - auto index = std_e::interval_index(id,old_offsets); - auto old_offset = old_offsets[index]; - auto new_offset = new_offsets[index]; - id = id - old_offset + new_offset; - } - } - } - - // 2. merge element sections of the same type - std::vector merged_sections; - auto section_current = begin(elt_sections); - auto elt_type = element_type(*section_current); - auto is_same_elt_type = [&elt_type](const auto& elt_node){ return element_type(elt_node) == elt_type; }; - while (section_current != end(elt_sections)){ - auto section_same_type_end = std::partition_point(section_current,end(elt_sections),is_same_elt_type); - auto new_section = merge_same_type_elt_sections(section_current,section_same_type_end,comm); - merged_sections.emplace_back(std::move(new_section)); - section_current = section_same_type_end; - if (section_current != end(elt_sections)) { - elt_type = element_type(*section_current); - } - } - - rm_children_by_label(z,"Elements_t"); - emplace_children(z,std::move(merged_sections)); -} - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/rearrange_element_sections/rearrange_element_sections.hpp b/maia/algo/dist/rearrange_element_sections/rearrange_element_sections.hpp deleted file mode 100644 index 2787f129..00000000 --- a/maia/algo/dist/rearrange_element_sections/rearrange_element_sections.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include "mpi.h" - - -namespace maia { - -auto rearrange_element_sections(cgns::tree& b, MPI_Comm comm) -> void; - -} // maia diff --git a/maia/algo/dist/redistribute.py b/maia/algo/dist/redistribute.py deleted file mode 100644 index d9c9d11f..00000000 --- a/maia/algo/dist/redistribute.py +++ /dev/null @@ -1,231 +0,0 @@ -import mpi4py.MPI as MPI -import numpy as np - -import maia -import maia.pytree as PT -import maia.pytree.maia as MT -import maia.transfer.protocols as MTP - -from maia.io.distribution_tree import interpret_policy - - -# --------------------------------------------------------------------------------------- -def redistribute_pl_node(node, distribution, comm): - """ - Redistribute a standard node having a PointList (and its childs) over several processes, - using a given distribution function. Mainly useful for unit tests. Node must be known by - each process. - """ - node_distrib = MT.getDistribution(node, 'Index')[1] - new_distrib = distribution(node_distrib[2], comm) - new_size = new_distrib[1] - new_distrib[0] - MT.newDistribution({'Index' : new_distrib}, node) - - #PL and PLDonor - for array_n in PT.get_children_from_predicate(node, 'IndexArray_t'): - idx_dimension = array_n[1].shape[0] - new_pl = np.empty((idx_dimension, new_size), order='F', dtype=array_n[1].dtype) - for ip in range(idx_dimension): - new_pl[ip,:] = MTP.block_to_block(np.ascontiguousarray(array_n[1][ip]), node_distrib, new_distrib, comm) - array_n[1] = new_pl - - #Data Arrays - has_subset = lambda n : PT.get_child_from_name(n, 'PointList') is not None or PT.get_child_from_name(n, 'PointRange') is not None - bcds_without_pl = lambda n : PT.get_label(n) == 'BCDataSet_t' and not has_subset(n) - bcds_without_pl_query = [bcds_without_pl, 'BCData_t', 'DataArray_t'] - for array_path in ['DataArray_t', 'BCData_t/DataArray_t', bcds_without_pl_query]: - for array_n in PT.iter_children_from_predicates(node, array_path): - array_n[1] = MTP.block_to_block(array_n[1], node_distrib, new_distrib, comm) - - #Additionnal treatement for subnodes with PL (eg bcdataset) - has_pl = lambda n : PT.get_name(n) not in ['PointList', 'PointRange'] and has_subset(n) - for child in [node for node in PT.get_children(node) if has_pl(node)]: - redistribute_pl_node(child, distribution, comm) - -# --------------------------------------------------------------------------------------- - - -# --------------------------------------------------------------------------------------- -def redistribute_data_node(node, distri, new_distri, comm): - """ - Distribute a standard node having arrays supported by allCells or allVertices over several processes, - using given distribution. Mainly useful for unit tests. Node must be known by each process. - """ - assert PT.get_node_from_name(node, 'PointList') is None - - for array in PT.iter_children_from_label(node, 'DataArray_t'): - array[1] = MTP.block_to_block(array[1], distri, new_distri, comm) - -# --------------------------------------------------------------------------------------- - - -# --------------------------------------------------------------------------------------- -def redistribute_elements_node(node, distribution, comm): - - assert PT.get_label(node) == 'Elements_t' - - has_eso = PT.Element.CGNSName(node) in ['NGON_n', 'NFACE_n', 'MIXED'] - - # Get element distribution - elt_distrib = MT.getDistribution(node, "Element")[1] - n_elt = elt_distrib[2] - - # New element distribution - new_elt_distrib = distribution(n_elt, comm) - new_distrib = {'Element': new_elt_distrib} - - # > ElementStartOffset - if has_eso : - ec_distrib = MT.getDistribution(node, "ElementConnectivity")[1] - - eso_n = PT.get_child_from_name(node, 'ElementStartOffset') - eso = PT.get_value(eso_n) - - # To be consistent with initial distribution, send everything excepted last elt - eso_wo_last = MTP.block_to_block(eso[:-1], elt_distrib, new_elt_distrib, comm) - - # Now we have to recover the last on each proc (with is the first - # of the next proc *having data*) - - # Each rank gather its first element of eso array - bound_elt = eso_wo_last[0] if eso_wo_last.size > 0 else -1 - all_bound_elt = np.empty(comm.Get_size()+1, dtype=eso.dtype) - all_bound_elt_view = all_bound_elt[:-1] - comm.Allgather(np.array([bound_elt], dtype=eso.dtype), all_bound_elt_view) - all_bound_elt[-1] = ec_distrib[2] - - eso_gather = np.empty(eso_wo_last.size+1, eso_wo_last.dtype) - eso_gather[:-1] = eso_wo_last - # Now search the start of the next rank having data - j = comm.Get_rank() + 1 - while (all_bound_elt[j] == -1): - j += 1 - eso_gather[-1] = all_bound_elt[j] - - PT.set_value(eso_n, eso_gather) - - new_ec_distrib = np.copy(ec_distrib) - new_ec_distrib[0] = eso_gather[0] - new_ec_distrib[1] = eso_gather[-1] - new_distrib['ElementConnectivity'] = new_ec_distrib - - else: - ec_distrib = elt_distrib*PT.Element.NVtx(node) - new_ec_distrib = new_elt_distrib*PT.Element.NVtx(node) - - # > Set CGNS#Distribution node in node - MT.newDistribution(new_distrib, node) - - # > ElementConnectivity - ec_n = PT.get_child_from_name(node, 'ElementConnectivity') - ec = PT.get_value(ec_n) - new_ec = MTP.block_to_block(ec, ec_distrib, new_ec_distrib, comm) - PT.set_value(ec_n, new_ec) - - # > ParentElement - pe_n = PT.get_child_from_name(node, 'ParentElements') - if pe_n is not None : - pe = PT.get_value(pe_n) - new_pe = np.zeros((new_elt_distrib[1]-new_elt_distrib[0], pe.shape[1]), order='F', dtype=pe.dtype) - for ip in range(pe.shape[1]): - new_pe_tmp = MTP.block_to_block(pe[:,ip], elt_distrib, new_elt_distrib, comm) - new_pe[:,ip] = new_pe_tmp - PT.set_value(pe_n, new_pe) - -# --------------------------------------------------------------------------------------- - - -# --------------------------------------------------------------------------------------- -def redistribute_zone(zone, distribution, comm): - - # Get distribution - old_distrib = {'Vertex' : MT.getDistribution(zone, "Vertex")[1], - 'Cell' : MT.getDistribution(zone, "Cell")[1]} - if PT.Zone.Type(zone) == 'Structured': - old_distrib['Face'] = MT.getDistribution(zone, "Face")[1] - - # New distribution - new_distrib = {'Vertex' : distribution(PT.Zone.n_vtx(zone) , comm), - 'Cell' : distribution(PT.Zone.n_cell(zone), comm)} - if PT.Zone.Type(zone) == 'Structured': - new_distrib['Face'] = distribution(PT.Zone.n_face(zone), comm) - - MT.newDistribution(new_distrib, zone) - - # > Coords - grid_coords = PT.get_children_from_label(zone, 'GridCoordinates_t') - for grid_coord in grid_coords: - redistribute_data_node(grid_coord, old_distrib['Vertex'], new_distrib['Vertex'], comm) - - # > Elements - elts = PT.get_children_from_label(zone, 'Elements_t') - for elt in elts: - redistribute_elements_node(elt, distribution, comm) - - - # > Flow Solutions - sols = PT.get_children_from_label(zone, 'FlowSolution_t') + PT.get_children_from_label(zone, 'DiscreteData_t') - for sol in sols: - if PT.get_child_from_name(sol, 'PointList') is None: - loc = PT.Subset.GridLocation(sol).replace('Center', '') # Remove 'Center' to use loc as dict key - redistribute_data_node(sol, old_distrib[loc], new_distrib[loc], comm) - else: - redistribute_pl_node(sol, distribution, comm) - - # > ZoneSubRegion (Do it before BC_t because we need old BC distribution) - zone_subregions = PT.get_children_from_label(zone, 'ZoneSubRegion_t') - for zone_subregion in zone_subregions: - # Trick if related to an other node -> add pl - matching_region_path = PT.Subset.ZSRExtent(zone_subregion, zone) - if matching_region_path != PT.get_name(zone_subregion): - distri_node = PT.get_node_from_path(zone, matching_region_path + '/:CGNS#Distribution') - PT.add_child(zone_subregion, PT.deep_copy(distri_node)) - redistribute_pl_node(zone_subregion, distribution, comm) - if matching_region_path != PT.get_name(zone_subregion): - PT.rm_child(zone_subregion, MT.getDistribution(zone_subregion)) - - # > BCs - for bc in PT.iter_children_from_predicates(zone, 'ZoneBC_t/BC_t'): - redistribute_pl_node(bc, distribution, comm) - - # > GCs - gc_pred = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - for gc in PT.iter_children_from_predicates(zone, ['ZoneGridConnectivity_t', gc_pred]): - redistribute_pl_node(gc, distribution, comm) - - -# --------------------------------------------------------------------------------------- - - -# --------------------------------------------------------------------------------------- -def redistribute_tree(dist_tree, policy, comm): - """ Redistribute the data of the input tree according to the choosen distribution policy. - - Supported policies are: - - - ``uniform`` : each data array is equally reparted over all the processes - - ``gather.N`` : all the data are moved on process N - - ``gather`` : shortcut for ``gather.0`` - - In both case, tree structure remains unchanged on all the processes - (the tree is still a valid distributed tree). - Input is modified inplace. - - Args: - dist_tree (CGNSTree) : Distributed tree - policy (str) : distribution policy (see above) - comm (MPIComm) : MPI communicator - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #redistribute_dist_tree@start - :end-before: #redistribute_dist_tree@end - :dedent: 2 - """ - distribution = interpret_policy(policy, comm) - - for zone in PT.iter_all_Zone_t(dist_tree): - redistribute_zone(zone, distribution, comm) - -# --------------------------------------------------------------------------------------- -# ======================================================================================= diff --git a/maia/algo/dist/remove_element.py b/maia/algo/dist/remove_element.py deleted file mode 100644 index 21b8c2d5..00000000 --- a/maia/algo/dist/remove_element.py +++ /dev/null @@ -1,209 +0,0 @@ -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import np_utils, par_utils -from maia.transfer import protocols as EP - -from .merge_ids import remove_distributed_ids - -def remove_element(zone, element): - """ - Remove one Element_t node from the Elements of the zone and perform the following - operations : - - Shift other elements ElementRange - - Update other elements ParentElements - - Update element supported PointLists - Note : PointListDonor are not currently supported - This function only shift arrays. Trouble will occurs later if trying to remove - elements that are indexed by a PointList, a ParentElement or anything else. - """ - target_range = PT.Element.Range(element) - target_size = PT.Element.Size(element) - for elem in PT.get_children_from_label(zone, 'Elements_t'): - if elem != element: - #Shift ER - er = PT.Element.Range(elem) - if target_range[0] < er[0]: - er -= target_size - - elem_pe_n = PT.get_child_from_name(elem, 'ParentElements') - if elem_pe_n is not None: - elem_pe = PT.get_value(elem_pe_n) - # This will raise if PE actually refers to the section to remove - assert not np_utils.any_in_range(elem_pe, *target_range), \ - f"Can not remove element {PT.get_name(element)}, indexed by {PT.get_name(elem)}/PE" - # We shift the element index of the sections that have been shifted - elem_pe -= target_size * (target_range[0] < elem_pe) - - #Shift pointList - subset_pathes = ['ZoneBC_t/BC_t', 'ZoneBC_t/BC_t/BCDataSet_t', 'ZoneGridConnectivity_t/GridConnectivity_t',\ - 'FlowSolution_t', 'ZoneSubRegion_t'] - for subset_path in subset_pathes: - for subset in PT.iter_children_from_predicates(zone, subset_path): - pl_n = PT.get_child_from_name(subset, 'PointList') - if PT.Subset.GridLocation(subset) != 'Vertex' and pl_n is not None: - pl = pl_n[1] - #Ensure that PL is not refering to section to remove - assert not np_utils.any_in_range(pl, *target_range), \ - f"Can not remove element {PT.get_name(element)}, indexed by at least one PointList" - #Shift - pl -= target_size * (target_range[0] < pl) - - PT.rm_child(zone, element) - -def remove_ngons(dist_ngon, ngon_to_remove, comm): - """ - Remove a list of NGon in an NGonElements distributed node. - Global data such as distribution are updated. - ngon_to_remove is the list of the ids of the ngon to be remove, *local*, - (start at 0), each proc having its own list (and it must know these ngons!) - - ! This function only works on NGon : in particular, vertices or - PointList are not removed - """ - ngon_to_remove = np.asarray(ngon_to_remove) - - pe_n = PT.get_child_from_name(dist_ngon, 'ParentElements') - eso_n = PT.get_child_from_name(dist_ngon, 'ElementStartOffset') - ec_n = PT.get_child_from_name(dist_ngon, 'ElementConnectivity') - er_n = PT.get_child_from_name(dist_ngon, 'ElementRange') - - local_eso = eso_n[1] - eso_n[1][0] #Make working ElementStartOffset start at 0 - new_pe = np.delete(pe_n[1], ngon_to_remove, axis=0) #Remove faces in PE - - ec_to_remove = np_utils.multi_arange(local_eso[ngon_to_remove], local_eso[ngon_to_remove+1]) - PT.set_value(ec_n, np.delete(ec_n[1], ec_to_remove)) - - # Eso is more difficult, we have to shift when deleting, locally and then globally - new_local_eso = np_utils.sizes_to_indices(np.delete(np.diff(local_eso), ngon_to_remove)) - - new_eso_shift = par_utils.gather_and_shift(new_local_eso[-1], comm) - new_eso = new_local_eso + new_eso_shift[comm.Get_rank()] - PT.set_value(eso_n, new_eso) - - #Update distributions - n_rmvd_local = len(ngon_to_remove) - n_rmvd_offset = par_utils.gather_and_shift(n_rmvd_local, comm) - n_rmvd_total = n_rmvd_offset[-1] - - n_rmvd_ec_local = len(ec_to_remove) - n_rmvd_ec_offset = par_utils.gather_and_shift(n_rmvd_ec_local, comm) - n_rmvd_ec_total = n_rmvd_ec_offset[-1] - - ngon_distri = PT.get_value(MT.getDistribution(dist_ngon, 'Element')) - ngon_distri[0] -= n_rmvd_offset[comm.Get_rank()] - ngon_distri[1] -= (n_rmvd_offset[comm.Get_rank()] + n_rmvd_local) - ngon_distri[2] -= n_rmvd_total - - ngon_distri_ec_n = MT.getDistribution(dist_ngon, 'ElementConnectivity') - if ngon_distri_ec_n is not None: - ngon_distri_ec = PT.get_value(ngon_distri_ec_n) - ngon_distri_ec[0] -= n_rmvd_ec_offset[comm.Get_rank()] - ngon_distri_ec[1] -= (n_rmvd_ec_offset[comm.Get_rank()] + n_rmvd_ec_local) - ngon_distri_ec[2] -= n_rmvd_ec_total - - # If NGon were first in tree, cell range has moved so pe must be offseted - if er_n[1][0] == 1: - np_utils.shift_nonzeros(new_pe, -n_rmvd_total) - PT.set_value(pe_n, new_pe) - - #Update ElementRange and size data (global) - er_n[1][1] -= n_rmvd_total - -def remove_elts_from_pl(zone, elt_n, elt_pl, comm): - """ - For a given Elements_t node, remove the entity spectified - in the distributed array `elt_pl`. - Elt_pl follows cgns convention, must be included in ElementRange - bounds of the given element node. - The Elements node it self is updated, as well as the following data: - - Zone dimension & distribution - - BCs - - Note: Zone must have decreasing element ordering - """ - DIM_TO_LOC = ['Vertex', 'EdgeCenter', 'FaceCenter', 'CellCenter'] - - # > Get element information - elt_dim = PT.Element.Dimension(elt_n) - elt_size = PT.Element.NVtx(elt_n) - elt_offset = PT.Element.Range(elt_n)[0] - - is_elt_bc = lambda n: PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n)==DIM_TO_LOC[elt_dim] - - ec_n = PT.get_child_from_name(elt_n, 'ElementConnectivity') - ec = PT.get_value(ec_n) - er_n = PT.get_child_from_name(elt_n, 'ElementRange') - er = PT.get_value(er_n) - old_er = np.copy(er) - if elt_pl.size != 0: - assert er[0]<=np.min(elt_pl) and np.max(elt_pl)<=er[1] - - elt_distrib_n = PT.maia.getDistribution(elt_n, distri_name='Element') - elt_distri = elt_distrib_n[1] - - # > Get old_to_new indirection, -1 means that elt must be removed - elt_pl_shifted = elt_pl - elt_offset + 1 # Elt_pl, but starting at 1 (remove cgns offset) - old_to_new_elt = remove_distributed_ids(elt_distri, elt_pl_shifted, comm) - ids_to_remove = np.where(old_to_new_elt==-1)[0] - - n_elt_to_rm_l = ids_to_remove.size - rm_distrib = par_utils.dn_to_distribution(n_elt_to_rm_l, comm) - n_elt_to_rm = rm_distrib[2] - - # > Updating element range, connectivity and distribution - ec_ids = np_utils.interweave_arrays([elt_size*ids_to_remove+i_size for i_size in range(elt_size)]) - ec = np.delete(ec, ec_ids) - PT.set_value(ec_n, ec) - er[1] -= n_elt_to_rm - - n_elt = elt_distri[1]-elt_distri[0] - new_elt_distrib = par_utils.dn_to_distribution(n_elt-n_elt_to_rm_l, comm) - if new_elt_distrib[2]==0: - PT.rm_child(zone, elt_n) - else: - PT.set_value(elt_distrib_n, new_elt_distrib) - - - # > Update BC PointList related to removed element node - zone_bc_n = PT.get_child_from_label(zone, 'ZoneBC_t') - for bc_n in PT.get_children_from_predicate(zone_bc_n, is_elt_bc): - bc_pl_n = PT.get_child_from_name(bc_n, 'PointList') - bc_pl = PT.get_value(bc_pl_n)[0] - mask_in_elt = (old_er[0] <= bc_pl ) & (bc_pl<=old_er[1]) - - # > Update numbering of PointList defined over other element nodes - new_bc_pl = bc_pl - bc_elt_ids = new_bc_pl[mask_in_elt]-elt_offset+1 - new_gn = EP.block_to_part(old_to_new_elt, elt_distri, [bc_elt_ids], comm)[0] - new_gn[new_gn>0] += elt_offset-1 - if mask_in_elt.any(): - new_bc_pl[mask_in_elt] = new_gn - new_bc_pl = new_bc_pl[new_bc_pl>0] - - new_bc_distrib = par_utils.dn_to_distribution(new_bc_pl.size, comm) - - # > Update BC - if new_bc_distrib[2]==0: - PT.rm_child(zone_bc_n, bc_n) - else: - PT.set_value(bc_pl_n, new_bc_pl.reshape((1,-1), order='F')) - PT.maia.newDistribution({'Index' : new_bc_distrib}, bc_n) - - # > Update zone size and distribution - if elt_dim==PT.Zone.CellDimension(zone): - zone[1][:,1] -= n_elt_to_rm - distri_cell = PT.maia.getDistribution(zone, 'Cell')[1] - distri_cell -= rm_distrib - - # > Shift other Element Range, if the have higher ids - for elt_n in PT.get_nodes_from_predicate(zone, 'Elements_t'): - elt_range = PT.Element.Range(elt_n) - if elt_range[0] > old_er[1]: - elt_range -= n_elt_to_rm - # > Shift PointList related to elements with higher ids - for bc_n in PT.get_nodes_from_predicates(zone, 'ZoneBC_t/BC_t'): - pl = PT.get_child_from_name(bc_n, 'PointList')[1] - pl[old_er[1]sub_point_range - # When we swap the PR, we must swap the corresponding dim of the PRD as well - dir_to_swap = (point_range_loc[:,1] < point_range_loc[:,0]) - opp_dir_to_swap = np.empty_like(dir_to_swap) - opp_dir_to_swap[abs(loc_transform) - 1] = dir_to_swap - - point_range_loc[dir_to_swap, 0], point_range_loc[dir_to_swap, 1] = \ - point_range_loc[dir_to_swap, 1], point_range_loc[dir_to_swap, 0] - point_range_opp_loc[opp_dir_to_swap,0], point_range_opp_loc[opp_dir_to_swap,1] \ - = point_range_opp_loc[opp_dir_to_swap,1], point_range_opp_loc[opp_dir_to_swap,0] - - bnd_axis = PT.Subset.normal_axis(PT.new_GridConnectivity1to1(point_range=point_range_loc)) - bnd_axis_opp = PT.Subset.normal_axis(PT.new_GridConnectivity1to1(point_range=point_range_opp_loc)) - #Compute slabs from attended location (better load balance) - gc_size = pr_utils.transform_bnd_pr_size(point_range_loc, "Vertex", output_loc) - gc_range = py_utils.uniform_distribution_at(gc_size.prod(), i_rank, n_rank) - gc_slabs = HFR2S.compute_slabs(gc_size, gc_range) - - sub_pr_list = [np.asarray(slab) for slab in gc_slabs] - #Compute sub pointranges from slab - for sub_pr in sub_pr_list: - sub_pr[:,0] += point_range_loc[:,0] - sub_pr[:,1] += point_range_loc[:,0] - 1 - - #Get opposed sub point ranges - sub_pr_opp_list = [] - for sub_pr in sub_pr_list: - sub_pr_opp = PT.utils._gc_transform_window(sub_pr, point_range_loc[:,0], point_range_opp_loc[:,0], T) - sub_pr_opp_list.append(sub_pr_opp) - - #If output location is vertex, sub_point_range are ready. Otherwise, some corrections are required - shift = pr_utils.normal_index_shift(point_range_loc, n_vtx_loc, bnd_axis, "Vertex", output_loc) - shift_opp = pr_utils.normal_index_shift(point_range_opp_loc, n_vtx_opp_loc, bnd_axis_opp, "Vertex", output_loc) - for i_pr in range(len(sub_pr_list)): - sub_pr_list[i_pr][bnd_axis,:] += shift - sub_pr_opp_list[i_pr][bnd_axis_opp,:] += shift_opp - - #When working on cell|face, extra care has to be taken if PR[:,1] < PR[:,0] : the cell|face id - #is not given by the bottom left corner but by the top right. We can just shift to retrieve casual behaviour - if 'Center' in output_loc: - for sub_pr_opp in sub_pr_opp_list: - reverted = np.sum(T, axis=1) < 0 - reverted[bnd_axis_opp] = False - sub_pr_opp[reverted,:] -= 1 - - # If the axes of opposite PointRange occurs in reverse order, vect. loop must be reverted thanks to order - loc_transform_2d = np.abs(np.delete(loc_transform, bnd_axis)) - order = 'C' if loc_transform_2d[0] > loc_transform_2d[1] else 'F' - - _loc, _loc_opp = _s_location(output_loc, bnd_axis), _s_location(output_loc, bnd_axis_opp) - point_list_loc = pr_utils.compute_pointList_from_pointRanges(sub_pr_list, n_vtx_loc, _loc) - point_list_opp_loc = pr_utils.compute_pointList_from_pointRanges(sub_pr_opp_list, n_vtx_opp_loc, _loc_opp, order) - - if gc_is_reference(gc_s, zone_path): - point_list, point_list_opp = point_list_loc, point_list_opp_loc - else: - point_list, point_list_opp = point_list_opp_loc, point_list_loc - - gc_u = PT.new_GridConnectivity(PT.get_name(gc_s), PT.get_value(gc_s), type='Abutting1to1', loc=output_loc) - PT.new_IndexArray('PointList' , point_list, parent=gc_u) - PT.new_IndexArray('PointListDonor', point_list_opp, parent=gc_u) - MT.newDistribution({'Index' : np.array([*gc_range, gc_size.prod()], pdm_gnum_dtype)}, parent=gc_u) - #Copy these nodes to gc_u - allowed_types = ['GridConnectivityProperty_t'] - allowed_names = ['GridConnectivityDonorName'] - for child in PT.get_children(gc_s): - if PT.get_name(child) in allowed_names or PT.get_label(child) in allowed_types: - PT.add_child(gc_u, child) - return gc_u -############################################################################### - -############################################################################### -def zonedims_to_ngon(n_vtx_zone, comm): - """ - Generates distributed NGonElement node from the number of - vertices in the zone. - """ - i_rank = comm.Get_rank() - n_rank = comm.Get_size() - - n_cell_zone = n_vtx_zone - 1 - - nf_i, nf_j, nf_k = n_face_per_dir(n_vtx_zone, n_cell_zone) - n_face_tot = nf_i + nf_j + nf_k - face_distri = py_utils.uniform_distribution_at(n_face_tot, i_rank, n_rank) - - n_face_loc = face_distri[1] - face_distri[0] - #Bounds stores for each proc [id of first iface, id of first jface, id of first kface, id of last kface] - # assuming that face are globally ordered i, then j, then k - bounds = np.empty(4, dtype=n_cell_zone.dtype) - bounds[0] = face_distri[0] - bounds[1] = bounds[0] - if bounds[0] < nf_i: - bounds[1] = min(face_distri[1], nf_i) - bounds[2] = bounds[1] - if nf_i <= bounds[1] and bounds[1] < nf_i + nf_j: - bounds[2] = min(face_distri[1], nf_i + nf_j) - bounds[3] = face_distri[1] - - assert bounds[3]-bounds[0] == n_face_loc - - - face_vtx_idx = 4*np.arange(face_distri[0], face_distri[1]+1, dtype=pdm_gnum_dtype) - face_vtx, face_pe = s_numbering.ngon_dconnectivity_from_gnum(bounds+1,n_cell_zone, pdm_gnum_dtype) - - _erange = np.array([1, n_face_tot], dtype=pdm_gnum_dtype) - ngon = PT.new_NGonElements('NGonElements', erange=_erange, eso=face_vtx_idx, ec=face_vtx, pe=face_pe) - - cg_face_distri = np.array([*face_distri, n_face_tot], dtype=pdm_gnum_dtype) - MT.newDistribution({'Element' : cg_face_distri, 'ElementConnectivity' : 4*cg_face_distri}, parent=ngon) - - return ngon -############################################################################### - -############################################################################### -def convert_s_to_u(dist_tree, connectivity, comm, subset_loc=dict()): - """Performs the destructuration of the input ``dist_tree``. - - Tree is modified in place: a NGON_n or HEXA_8 (not yet implemented) - connectivity is generated, and the following subset nodes are converted: - BC_t, BCDataSet_t and GridConnectivity1to1_t. - - Note: - Exists also as :func:`convert_s_to_ngon()` with connectivity set to - NGON_n and subset_loc set to FaceCenter. - - Args: - dist_tree (CGNSTree): Structured tree - connectivity (str): Type of elements used to describe the connectivity. - Admissible values are ``"NGON_n"`` and ``"HEXA"`` (not yet implemented). - comm (MPIComm) : MPI communicator - subset_loc (dict, optional): - Expected output GridLocation for the following subset nodes: BC_t, GC_t. - For each label, output location can be a single location value, a list - of locations or None to preserve the input value. Defaults to None. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #convert_s_to_u@start - :end-before: #convert_s_to_u@end - :dedent: 2 - """ - n_rank = comm.Get_size() - i_rank = comm.Get_rank() - - if not os.environ.get('MAIA_SILENT_API_WARNINGS') and i_rank == 0: - mlog.warning("API change -- convert_s_to_u and convert_s_to_ngon functions now operate inplace, " - "and will return None in next release. " - "Export MAIA_SILENT_API_WARNINGS=1 to remove this warning.") - - zone_path_to_vertex_size = {path: PT.Zone.VertexSize(PT.get_node_from_path(dist_tree, path)) - for path in PT.predicates_to_paths(dist_tree, 'CGNSBase_t/Zone_t')} - - PT.update_child(dist_tree, 'CGNSLibraryVersion', 'CGNSLibraryVersion_t', 4.2) - for base in PT.iter_all_CGNSBase_t(dist_tree): - for zone in PT.iter_all_Zone_t(base): - if PT.Zone.Type(zone) == 'Unstructured': #Zone is already U - continue - - elif PT.Zone.Type(zone) == 'Structured': #Zone is S -> convert it - zone_dims_s = PT.get_value(zone) - zone_dims_u = np.prod(zone_dims_s, axis=0, dtype=zone_dims_s.dtype).reshape(1,-1) - n_vtx = PT.Zone.VertexSize(zone) - - PT.update_child(zone, 'ZoneType', 'ZoneType_t', 'Unstructured') - PT.set_value(zone, zone_dims_u) - - for flow_solution_s in PT.iter_children_from_label(zone, "FlowSolution_t"): - patch = PT.get_child_from_predicate(flow_solution_s, lambda n: PT.get_name(n) in ['PointRange', 'PointList']) - assert patch is None, f"Partial FlowSolution_t are not supported" - - PT.add_child(zone, zonedims_to_ngon(n_vtx, comm)) - - loc_to_name = {'Vertex' : '#Vtx', 'FaceCenter': '#Face', 'CellCenter': '#Cell'} - zonebc_s = PT.get_child_from_label(zone, "ZoneBC_t") - if zonebc_s is not None: - bc_u_list = list() - for bc_s in PT.iter_children_from_label(zonebc_s, "BC_t"): - out_loc_l = get_output_loc(subset_loc, bc_s) - for out_loc in out_loc_l: - suffix = loc_to_name[out_loc] if len(out_loc_l) > 1 else '' - bc_u = bc_s_to_bc_u(bc_s, n_vtx, out_loc, i_rank, n_rank) - PT.set_name(bc_u, PT.get_name(bc_u) + suffix) - bc_u_list.append(bc_u) - # Replace bcs S -> bcs U - PT.rm_children_from_label(zonebc_s, 'BC_t') - PT.get_children(zonebc_s).extend(bc_u_list) - - zone_path = '/'.join([PT.get_name(base), PT.get_name(zone)]) - for zonegc_s in PT.iter_children_from_label(zone, "ZoneGridConnectivity_t"): - gc_u_list = list() - for gc_s in PT.iter_children_from_label(zonegc_s, "GridConnectivity1to1_t"): - out_loc_l = get_output_loc(subset_loc, gc_s) - opp_zone_path = PT.GridConnectivity.ZoneDonorPath(gc_s, PT.get_name(base)) - n_vtx_opp = zone_path_to_vertex_size[opp_zone_path] - for out_loc in out_loc_l: - suffix = loc_to_name[out_loc] if len(out_loc_l) > 1 else '' - gc_u = gc_s_to_gc_u(gc_s, zone_path, n_vtx, n_vtx_opp, out_loc, i_rank, n_rank) - PT.set_name(gc_u, PT.get_name(gc_u) + suffix) - gc_u_list.append(gc_u) - #Manage not 1to1 gcs as BCs - is_abutt = lambda n : PT.get_label(n) == 'GridConnectivity_t' and PT.GridConnectivity.Type(n) == 'Abutting' - for gc_s in PT.iter_children_from_predicate(zonegc_s, is_abutt): - out_loc_l = get_output_loc(subset_loc, gc_s) - for out_loc in out_loc_l: - suffix = loc_to_name[out_loc] if len(out_loc_l) > 1 else '' - gc_u = bc_s_to_bc_u(gc_s, n_vtx, out_loc, i_rank, n_rank) - PT.set_name(gc_u, PT.get_name(gc_u) + suffix) - PT.new_GridConnectivityType('Abutting', gc_u) - gc_u_list.append(gc_u) - - # Hybrid joins should be here : we just have to translate the PL ijk into face index - is_abutt1to1 = lambda n : PT.get_label(n) == 'GridConnectivity_t' and PT.GridConnectivity.Type(n) == 'Abutting1to1' - for gc_s in PT.iter_children_from_predicate(zonegc_s, is_abutt1to1): - opp_zone_path = PT.GridConnectivity.ZoneDonorPath(gc_s, PT.get_name(base)) - opp_zone = PT.get_node_from_path(dist_tree, opp_zone_path) - if PT.Zone.Type(opp_zone) != 'Unstructured': - continue - loc = PT.Subset.GridLocation(gc_s) - pl = PT.get_child_from_name(gc_s, 'PointList')[1] - pl_idx = s_numbering.ijk_to_index_from_loc(*pl, loc, zone_path_to_vertex_size[zone_path]) - pl_idx = pl_idx.reshape((1,-1), order='F') - if 'FaceCenter' in loc: #IFace, JFace or KFaceCenter -> FaceCenter - PT.update_child(gc_s, 'GridLocation', value='FaceCenter') - PT.update_child(gc_s, 'PointList', value=pl_idx) - # Now update PointListDonor of the opposite (already U) join - for opp_jn in PT.get_nodes_from_predicates(opp_zone, 'GridConnectivity_t'): - opp_base_name = PT.path_head(opp_zone_path,1) - if PT.GridConnectivity.ZoneDonorPath(opp_jn, opp_base_name) == zone_path: - pld_n = PT.get_child_from_name(opp_jn, 'PointListDonor') - if pld_n is not None and np.array_equal(pld_n[1], pl): - PT.update_child(opp_jn, 'PointListDonor', value=pl_idx) - break - else: - raise RuntimeError(f"Opposite join of {PT.get_name(gc_s)} (zone {zone_path}) has not been found") - - # Replace jns S -> jns U - PT.rm_children_from_predicate(zonegc_s, is_abutt) - PT.rm_children_from_label(zonegc_s, "GridConnectivity1to1_t") - PT.get_children(zonegc_s).extend(gc_u_list) - - # Face distribution does not exist on U meshes - distri = MT.getDistribution(zone) - PT.rm_children_from_name(distri, 'Face') - - return dist_tree -############################################################################### -def convert_s_to_ngon(disttree_s, comm): - """Shortcut to convert_s_to_u with NGon connectivity and FaceCenter subsets""" - return convert_s_to_u(disttree_s, - 'NGON_n', - comm, - {'BC_t' : 'FaceCenter', 'GC_t' : 'FaceCenter'}) - -def convert_s_to_poly(disttree_s, comm): - """Same as convert_s_to_ngon, but also creates the NFace connectivity""" - from maia.algo import pe_to_nface - disttree_u = convert_s_to_ngon(disttree_s, comm) - for z in PT.iter_all_Zone_t(disttree_u): - pe_to_nface(z,comm) - return disttree_u diff --git a/maia/algo/dist/split_boundary_subzones_according_to_bcs/__init__.py b/maia/algo/dist/split_boundary_subzones_according_to_bcs/__init__.py deleted file mode 100644 index 003ea202..00000000 --- a/maia/algo/dist/split_boundary_subzones_according_to_bcs/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from cmaia import dist_algo as cdist_algo -from maia.algo.apply_function_to_nodes import apply_to_bases,apply_to_zones - -from maia.utils import require_cpp20 - -@require_cpp20 -def split_boundary_subzones_according_to_bcs(t): - apply_to_bases(cdist_algo.split_boundary_subzones_according_to_bcs, t) - diff --git a/maia/algo/dist/split_boundary_subzones_according_to_bcs/split_boundary_subzones_according_to_bcs.cpp b/maia/algo/dist/split_boundary_subzones_according_to_bcs/split_boundary_subzones_according_to_bcs.cpp deleted file mode 100644 index 23f1e56e..00000000 --- a/maia/algo/dist/split_boundary_subzones_according_to_bcs/split_boundary_subzones_according_to_bcs.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/dist/split_boundary_subzones_according_to_bcs/split_boundary_subzones_according_to_bcs.hpp" -#include "cpp_cgns/cgns.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids.hpp" -#include -#include -#include "maia/factory/partitioning/gc_name_convention.hpp" -#include "std_e/utils/concatenate.hpp" -#include "maia/utils/parallel/distribution.hpp" -#include "maia/pytree/maia/element_sections.hpp" - -#include "maia/utils/parallel/exchange/block_to_part.hpp" - - -using namespace cgns; - - -namespace maia { - - -// TODO I4 -> I, R8 -> R -template auto -sub_field_for_ids(const Tree_range& fields, std_e::span ids, I4 first_id, const Distribution& distri, MPI_Comm comm) { - MPI_Barrier(comm); - MPI_Barrier(comm); - I4 sz = ids.size(); - - pdm::block_to_part_protocol btp_protocol(comm,distri,ids); - std::vector sub_field_nodes; - for (const tree& field_node : fields) { - MPI_Barrier(comm); - MPI_Barrier(comm); - auto dfield = get_value(field_node); - auto pfield = pdm::exchange(btp_protocol,dfield); - std::vector sub_field(sz); - std::copy(begin(pfield),end(pfield),begin(sub_field)); - - sub_field_nodes.push_back(cgns::new_DataArray(name(field_node),std::move(sub_field))); - } - - return sub_field_nodes; -} - -template auto -split_boundary_subzone_according_to_bcs(const tree& zsr, const Tree_range& bcs, MPI_Comm comm) { - I4 first_id = get_child_value_by_name(zsr,"PointRange")(0,0); - auto partial_dist = get_node_value_by_matching(zsr,":CGNS#Distribution/Index"); - auto distri = distribution_from_partial(partial_dist,comm); - std::vector sub_zsrs; - for (const tree& bc : bcs) { - auto& pl = get_child_by_name(bc,"PointList"); - auto fields_on_bc = sub_field_for_ids(get_children_by_label(zsr,"DataArray_t"),get_value(pl),first_id,distri,comm); - - auto sub_zsr = new_ZoneSubRegion(name(zsr)+"_"+name(bc),2,"FaceCenter"); - emplace_children(sub_zsr,std::move(fields_on_bc)); - emplace_child(sub_zsr,new_Descriptor("BCRegionName",name(bc))); - - sub_zsrs.push_back(std::move(sub_zsr)); - } - return sub_zsrs; -} - - - -auto -is_complete_bnd_zone_sub_region(const tree& t, const cgns::interval& elt_2d_range) -> bool { - if (label(t)!="ZoneSubRegion_t") return false; - if (GridLocation(t)!="FaceCenter") return false; - if (!has_child_of_name(t,"PointRange")) return false; - auto range = point_range_to_interval(get_child_by_name(t,"PointRange")); - if (range==elt_2d_range) return true; - return false; -} - -auto -is_bc_on_faces(const tree& t) -> bool { - if (label(t)!="BC_t") return false; - return GridLocation(t)=="FaceCenter"; -} - -auto -split_boundary_subzones_according_to_bcs(tree& b, MPI_Comm comm) -> void { - auto zs = get_children_by_label(b,"Zone_t"); - for (tree& z : zs) { - auto elt_2d = surface_element_sections(z); - auto elt_2d_range = elements_interval(elt_2d); - auto zsrs = get_children_by_predicate(z,[&](const tree& t){ return is_complete_bnd_zone_sub_region(t,elt_2d_range); }); - - auto& zbc = get_child_by_name(z,"ZoneBC"); - auto bcs = get_children_by_predicate(zbc,is_bc_on_faces); - - std::vector all_sub_zsrs; - for (const tree& zsr : zsrs) { - auto sub_zsrs = split_boundary_subzone_according_to_bcs(zsr,bcs,comm); - std_e::emplace_back(all_sub_zsrs,std::move(sub_zsrs)); - } - - // change children at the end (else, iterator invalidation) - rm_children(z,zsrs); - emplace_children(z,std::move(all_sub_zsrs)); - } -} - - -} // maia -#endif // C++>17 diff --git a/maia/algo/dist/split_boundary_subzones_according_to_bcs/split_boundary_subzones_according_to_bcs.hpp b/maia/algo/dist/split_boundary_subzones_according_to_bcs/split_boundary_subzones_according_to_bcs.hpp deleted file mode 100644 index 495594aa..00000000 --- a/maia/algo/dist/split_boundary_subzones_according_to_bcs/split_boundary_subzones_according_to_bcs.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" -#include - - -namespace maia { - -auto split_boundary_subzones_according_to_bcs(cgns::tree& b, MPI_Comm comm) -> void; - -} // maia diff --git a/maia/algo/dist/std_elements_to_mixed.py b/maia/algo/dist/std_elements_to_mixed.py deleted file mode 100644 index c4543c60..00000000 --- a/maia/algo/dist/std_elements_to_mixed.py +++ /dev/null @@ -1,94 +0,0 @@ -import mpi4py.MPI as mpi - -import numpy as np - -from maia import pytree as PT -from maia.transfer import protocols as MTP -from maia.utils import par_utils as MUPar - - -def convert_elements_to_mixed(dist_tree, comm): - """ - Transform an element based connectivity into a mixed connectivity. - - Tree is modified in place : standard elements are removed from the zones. - Note that the original ordering of elements is preserved. - - Args: - dist_tree (CGNSTree): Tree with connectivity described by standard elements - comm (`MPIComm`) : MPI communicator - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #convert_elements_to_mixed@start - :end-before: #convert_elements_to_mixed@end - :dedent: 2 - """ - rank = comm.Get_rank() - - for zone in PT.get_all_Zone_t(dist_tree): - part_data_ec = [] - part_data_eso = [] - part_stride_ec = [] - ln_to_gn_list = [] - nb_nodes_prev = 0 - nb_elem_prev = 0 - - - # 1/ Create local mixed connectivity and element start offeset tab for each element node - # and deduce the local number of each element type - for element in PT.Zone.get_ordered_elements(zone): - assert PT.Element.CGNSName(element) not in ['MIXED', 'NGON_n', 'NFACE_n'] - elem_type = PT.get_value(element)[0] - elem_er = PT.Element.Range(element) - elem_ec = PT.get_node_from_name(element,'ElementConnectivity',depth=1)[1] - elem_distrib = PT.maia.getDistribution(element, 'Element')[1] - - nb_nodes_per_elem = PT.Element.NVtx(element) - nb_elem_loc = elem_distrib[1]-elem_distrib[0] - - mixed_partial_ec = np.zeros(nb_elem_loc*(nb_nodes_per_elem+1),dtype = elem_ec.dtype) - mixed_partial_ec[::nb_nodes_per_elem+1] = elem_type - for i in range(nb_nodes_per_elem): - mixed_partial_ec[i+1::nb_nodes_per_elem+1] = elem_ec[i::nb_nodes_per_elem] - part_data_ec.append(mixed_partial_ec) - stride_ec = (nb_nodes_per_elem+1)*np.ones(nb_elem_loc,dtype = np.int32) - part_stride_ec.append(stride_ec) - - mixed_partial_eso = (nb_nodes_per_elem+1)*np.arange(nb_elem_loc,dtype = elem_ec.dtype) + \ - nb_nodes_prev + (nb_nodes_per_elem+1)*elem_distrib[0] - part_data_eso.append(mixed_partial_eso) - stride_eso = np.ones(nb_elem_loc,dtype = np.int32) - - ln_to_gn = np.array(range(nb_elem_loc),dtype=elem_distrib.dtype) + \ - nb_elem_prev + elem_distrib[0] + 1 - ln_to_gn_list.append(ln_to_gn) - - nb_nodes_prev += (nb_nodes_per_elem+1)*PT.Element.Size(element) - nb_elem_prev += (elem_er[1]-elem_er[0]+1) - - - # 2/ Delete standard nodes and add mixed nodes - PT.rm_nodes_from_label(zone,'Elements_t') - elem_distrib = MUPar.uniform_distribution(nb_elem_prev,comm) - ptb = MTP.PartToBlock(elem_distrib,ln_to_gn_list,comm) - - __, dist_data_eso_wo_last = ptb.exchange_field(part_data_eso) - dist_stride_ec, dist_data_ec = ptb.exchange_field(part_data_ec,part_stride_ec) - - dist_data_eso = np.empty(len(dist_data_eso_wo_last)+1,dtype=dist_data_eso_wo_last.dtype) - dist_data_eso[:-1] = dist_data_eso_wo_last - # If rank obtain data, the last value of local distributed ElementStartOffset is equal to - # the previous value of local ElementStartOffset plus the size of the last local element - # else, all the element have been already distributed and local distributed - # ElementStartOffset distribution is equal to the total number of elements - if len(dist_stride_ec) > 0: - dist_data_eso[-1] = dist_data_eso_wo_last[-1] + dist_stride_ec[-1] - else: - assert rank >= nb_elem_prev - dist_data_eso[-1] = nb_nodes_prev - - mixed = PT.new_Elements('Mixed','MIXED',erange=[1,nb_elem_prev],econn=dist_data_ec,parent=zone) - eso = PT.new_DataArray('ElementStartOffset',dist_data_eso,parent=mixed) - distri_ec = np.array((dist_data_eso[0],dist_data_eso[-1],nb_nodes_prev),dtype=elem_distrib.dtype) - PT.maia.newDistribution({'Element' : elem_distrib, 'ElementConnectivity' : distri_ec}, parent=mixed) diff --git a/maia/algo/dist/subset_tools.py b/maia/algo/dist/subset_tools.py deleted file mode 100644 index d730489e..00000000 --- a/maia/algo/dist/subset_tools.py +++ /dev/null @@ -1,67 +0,0 @@ -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.transfer import protocols as EP -from maia.utils import np_utils, par_utils - -from maia.utils.parallel import algo as par_algo - -def sort_dist_pointlist(subset, comm): - pl_n = PT.get_child_from_name(subset, 'PointList') - pld_n = PT.get_child_from_name(subset, 'PointListDonor') - - sorter = par_algo.DistSorter(pl_n[1][0], comm) - - dist_pl = sorter.sort(pl_n[1][0]) - PT.update_child(subset, 'PointList', value=dist_pl.reshape((1,-1), order='F')) - - if pld_n is not None: - dist_pld = sorter.sort(pld_n[1][0]) - PT.update_child(subset, 'PointListDonor', value=dist_pld.reshape((1,-1), order='F')) - - new_distri = par_utils.dn_to_distribution(dist_pl.size, comm) - MT.newDistribution({'Index' : new_distri}, subset) - -def vtx_ids_to_face_ids(vtx_ids, elt_n, comm, elt_full): - """ - From an array of vertex ids, search in the distributed NGon node - the id of faces constituted by these vertices. - If elt_full is True, only faces having all their vertices in vtx_ids - are returned. - Otherwise, faces having at least one vertex in vtx_ids are returned. - """ - i_rank = comm.Get_rank() - elt_distri = MT.getDistribution(elt_n, 'Element')[1] - delt_vtx = PT.get_child_from_name(elt_n, 'ElementConnectivity')[1] - if PT.Element.CGNSName(elt_n)=='NGON_n': - delt_vtx_idx = PT.get_child_from_name(elt_n, 'ElementStartOffset')[1] - else: - elt_size = PT.Element.NVtx(elt_n) - delt_vtx_idx = np.arange(elt_distri[0]*elt_size,(elt_distri[1]+1)*elt_size,elt_size, dtype=np.int32) - - # > Building PTP object, the graph between vtx indices and elt connectivity is what we need - delt_vtx_tag = par_algo.gnum_isin(delt_vtx, vtx_ids, comm) - - # Then reduce : select face if all its vertices have flag set to 1 - ufunc = np.logical_and if elt_full==True else np.logical_or - delt_vtx_idx_loc = delt_vtx_idx - delt_vtx_idx[0] - delt_vtx_tag = ufunc.reduceat(delt_vtx_tag, delt_vtx_idx_loc[:-1]) - face_ids = np.where(delt_vtx_tag)[0] + elt_distri[0] + 1 - - return np_utils.safe_int_cast(face_ids, vtx_ids.dtype) - -def convert_subset_as_facelist(dist_tree, subset_path, comm): - node = PT.get_node_from_path(dist_tree, subset_path) - zone_path = PT.path_head(subset_path, 2) - if PT.Subset.GridLocation(node) == 'Vertex': - zone = PT.get_node_from_path(dist_tree, zone_path) - pl_vtx = PT.get_child_from_name(node, 'PointList')[1][0] - face_list = vtx_ids_to_face_ids(pl_vtx, PT.Zone.NGonNode(zone), comm, True) - PT.update_child(node, 'GridLocation', value='FaceCenter') - PT.update_child(node, 'PointList', value=face_list.reshape((1,-1), order='F')) - MT.newDistribution({'Index' : par_utils.dn_to_distribution(face_list.size, comm)}, node) - elif PT.Subset.GridLocation(node) != 'FaceCenter': - raise ValueError(f"Unsupported location for subset {subset_path}") - diff --git a/maia/algo/dist/test/ngons_to_elements.test.cpp b/maia/algo/dist/test/ngons_to_elements.test.cpp deleted file mode 100644 index 39d24a9b..00000000 --- a/maia/algo/dist/test/ngons_to_elements.test.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#if __cplusplus > 201703L -#include "std_e/unit_test/doctest_pybind.hpp" - -#include "maia/io/file.hpp" -#include "maia/utils/mesh_dir.hpp" -#include "cpp_cgns/tree_manip.hpp" -#include "maia/algo/dist/elements_to_ngons/elements_to_ngons.hpp" - -#include "maia/__old/transform/convert_to_std_elements.hpp" - - -using cgns::I4; -using cgns::I8; -using cgns::tree; - - -PYBIND_TEST_CASE("convert_zone_to_std_elements") { - // setup - std::string file_name = maia::mesh_dir+"hex_2_prism_2.yaml"; - tree t = maia::file_to_dist_tree(file_name,MPI_COMM_SELF); - tree& z = cgns::get_node_by_matching(t,"Base/Zone"); - maia::elements_to_ngons(z,MPI_COMM_SELF); // Note: we are not testing that, its just a way to get an ngon test - - // apply tested function - maia::convert_zone_to_std_elements(z); - - // check - auto tri_elt_type = cgns::get_node_value_by_matching(z,"TRI_3")[0]; - auto tri_range = cgns::get_node_value_by_matching(z,"TRI_3/ElementRange"); - auto tri_connec = cgns::get_node_value_by_matching(z,"TRI_3/ElementConnectivity"); - - auto quad_elt_type = cgns::get_node_value_by_matching(z,"QUAD_4")[0]; - auto quad_range = cgns::get_node_value_by_matching(z,"QUAD_4/ElementRange"); - auto quad_connec = cgns::get_node_value_by_matching(z,"QUAD_4/ElementConnectivity"); - - auto penta_elt_type = cgns::get_node_value_by_matching(z,"PENTA_6")[0]; - auto penta_range = cgns::get_node_value_by_matching(z,"PENTA_6/ElementRange"); - auto penta_connec = cgns::get_node_value_by_matching(z,"PENTA_6/ElementConnectivity"); - - auto hexa_elt_type = cgns::get_node_value_by_matching(z,"HEXA_8")[0]; - auto hexa_range = cgns::get_node_value_by_matching(z,"HEXA_8/ElementRange"); - auto hexa_connec = cgns::get_node_value_by_matching(z,"HEXA_8/ElementConnectivity"); - - CHECK( tri_elt_type == cgns::TRI_3 ); - CHECK( tri_range == std::vector{1,2} ); - CHECK( tri_connec == std::vector{2,5,3,12,13,15} ); - - CHECK( quad_elt_type == cgns::QUAD_4 ); - CHECK( quad_range == std::vector{3,14} ); - CHECK( quad_connec == std::vector{1,6, 9,4, 6,11,14, 9, 3,5,10, 8, 8,10,15,13, - 1,2, 7,6, 2, 3, 8, 7, 6,7,12,11, 7, 8,13,12, - 4,9,10,5, 9,14,15,10, 1,4, 5, 2, 11,12,15,14} ); - - CHECK( penta_elt_type == cgns::PENTA_6 ); - CHECK( penta_range == std::vector{15,16} ); - CHECK( penta_connec == std::vector{3,5,2,8,10,7, 7,8,10,12,13,15} ); - - CHECK( hexa_elt_type == cgns::HEXA_8 ); - CHECK( hexa_range == std::vector{17,18} ); - CHECK( hexa_connec == std::vector{2,5,4,1,7,10,9,6, 6,7,10,9,11,12,15,14} ); -} -PYBIND_TEST_CASE("convert_zone_to_std_elements - all elt kinds") { - // setup - std::string file_name = maia::mesh_dir+"hex_prism_pyra_tet.yaml"; - tree t = maia::file_to_dist_tree(file_name,MPI_COMM_SELF); - tree& z = cgns::get_node_by_matching(t,"Base/Zone"); - maia::elements_to_ngons(z,MPI_COMM_SELF); // Note: we are not testing that, its just a way to get an ngon test - - // apply tested function - maia::convert_zone_to_std_elements(z); - - // check - auto tri_elt_type = cgns::get_node_value_by_matching(z,"TRI_3")[0]; - auto tri_range = cgns::get_node_value_by_matching(z,"TRI_3/ElementRange"); - auto tri_connec = cgns::get_node_value_by_matching(z,"TRI_3/ElementConnectivity"); - - auto quad_elt_type = cgns::get_node_value_by_matching(z,"QUAD_4")[0]; - auto quad_range = cgns::get_node_value_by_matching(z,"QUAD_4/ElementRange"); - auto quad_connec = cgns::get_node_value_by_matching(z,"QUAD_4/ElementConnectivity"); - - auto tetra_elt_type = cgns::get_node_value_by_matching(z,"TETRA_4")[0]; - auto tetra_range = cgns::get_node_value_by_matching(z,"TETRA_4/ElementRange"); - auto tetra_connec = cgns::get_node_value_by_matching(z,"TETRA_4/ElementConnectivity"); - - auto penta_elt_type = cgns::get_node_value_by_matching(z,"PENTA_6")[0]; - auto penta_range = cgns::get_node_value_by_matching(z,"PENTA_6/ElementRange"); - auto penta_connec = cgns::get_node_value_by_matching(z,"PENTA_6/ElementConnectivity"); - - auto pyra_elt_type = cgns::get_node_value_by_matching(z,"PYRA_5")[0]; - auto pyra_range = cgns::get_node_value_by_matching(z,"PYRA_5/ElementRange"); - auto pyra_connec = cgns::get_node_value_by_matching(z,"PYRA_5/ElementConnectivity"); - - auto hexa_elt_type = cgns::get_node_value_by_matching(z,"HEXA_8")[0]; - auto hexa_range = cgns::get_node_value_by_matching(z,"HEXA_8/ElementRange"); - auto hexa_connec = cgns::get_node_value_by_matching(z,"HEXA_8/ElementConnectivity"); - - CHECK( tri_elt_type == cgns::TRI_3 ); - CHECK( tri_range == std::vector{1,6} ); - CHECK( tri_connec == std::vector{6,11, 9, 8,10,11, 6, 7,11, - 7, 8,11, 2, 5, 3, 9,11,10} ); - - CHECK( quad_elt_type == cgns::QUAD_4 ); - CHECK( quad_range == std::vector{7,12} ); - CHECK( quad_connec == std::vector{1,6,9,4, 3,5,10,8, 1,2,7,6, - 2,3,8,7, 4,9,10,5, 1,4,5,2} ); - - CHECK( tetra_elt_type == cgns::TETRA_4 ); - CHECK( tetra_range == std::vector{13,13} ); - CHECK( tetra_connec == std::vector{7,8,10,11} ); - - CHECK( pyra_elt_type == cgns::PYRA_5 ); - CHECK( pyra_range == std::vector{14,14} ); - CHECK( pyra_connec == std::vector{6,7,10,9,11} ); - - CHECK( penta_elt_type == cgns::PENTA_6 ); - CHECK( penta_range == std::vector{15,15} ); - CHECK( penta_connec == std::vector{3,5,2,8,10,7} ); - - CHECK( hexa_elt_type == cgns::HEXA_8 ); - CHECK( hexa_range == std::vector{16,16} ); - CHECK( hexa_connec == std::vector{2,5,4,1,7,10,9,6} ); -} -#endif // C++>17 diff --git a/maia/algo/dist/test/test_adaptation_utils.py b/maia/algo/dist/test/test_adaptation_utils.py deleted file mode 100644 index 5a01af5d..00000000 --- a/maia/algo/dist/test/test_adaptation_utils.py +++ /dev/null @@ -1,337 +0,0 @@ -import pytest -import pytest_parallel -import shutil - -import maia -import maia.pytree as PT -import maia.algo.dist.adaptation_utils as adapt_utils -from maia.pytree.yaml import parse_yaml_cgns -from maia.utils import par_utils - -from maia import npy_pdm_gnum_dtype as pdm_dtype - -import numpy as np - -def gen_dist_zone(comm): - if comm.rank==0: - zone_n = PT.new_Zone('zone', type='Unstructured', size=[[9,5,0]]) - cx = np.array([1.,3.,5.,7.,9.]) - cy = np.array([0.,0.,0.,0.,0.]) - cz = np.array([0.,0.,0.,0.,0.]) - grid_coord_n = PT.new_GridCoordinates(fields={'CoordinateX':cx,'CoordinateY':cy,'CoordinateZ':cz}, parent=zone_n) - PT.maia.newDistribution({'Vertex':[0,5,9]}, parent=zone_n) - fields = {'cX':cx,'cY':cy,'cZ':cz} - flowsol_n = PT.new_FlowSolution('FSolution', loc='Vertex', fields=fields, parent=zone_n) - # PT.maia.newDistribution({'Index':[0,5,9]}, parent=flowsol_n) - - elif comm.rank==1: - zone_n = PT.new_Zone('zone', type='Unstructured', size=[[9,5,0]]) - cx = np.array([2.,4.,6.,8.]) - cy = np.array([0.,0.,0.,0.]) - cz = np.array([0.,0.,0.,0.]) - grid_coord_n = PT.new_GridCoordinates(fields={'CoordinateX':cx,'CoordinateY':cy,'CoordinateZ':cz}, parent=zone_n) - PT.maia.newDistribution({'Vertex':[5,9,9]}, parent=zone_n) - fields = {'cX':cx,'cY':cy,'cZ':cz} - flowsol_n = PT.new_FlowSolution('FSolution', loc='Vertex', fields=fields, parent=zone_n) - # PT.maia.newDistribution({'Index':[5,9,9]}, parent=flowsol_n) - - return zone_n - -@pytest_parallel.mark.parallel(2) -def test_duplicate_specified_vtx(comm): - zone_n = gen_dist_zone(comm) - - if comm.rank==0: - vtx_pl = np.array(np.array([7,9]), dtype=np.int32) - expected_cx = np.array([1., 3., 5., 7., 9., 2., 4.]) - elif comm.rank==1: - vtx_pl = np.array(np.array([2,4,8]), dtype=np.int32) - expected_cx = np.array([6., 8., 4., 8., 3., 7., 6.]) - - adapt_utils.duplicate_specified_vtx(zone_n, vtx_pl, comm) - - assert np.allclose(PT.get_value(PT.get_node_from_name(zone_n, 'CoordinateX')), expected_cx) - assert np.allclose(PT.get_value(PT.get_node_from_name(zone_n, 'cX')), expected_cx) - - new_distri = PT.maia.getDistribution(zone_n, 'Vertex')[1] - assert PT.Zone.n_vtx(zone_n)==14 - assert (maia.utils.par_utils.partial_to_full_distribution(new_distri, comm) == [0,7,14]).all() - - -@pytest_parallel.mark.parallel(2) -def test_remove_specified_vtx(comm): - zone_n = gen_dist_zone(comm) - - if comm.Get_rank()==0: - vtx_pl = np.array(np.array([7,9]), dtype=np.int32) - expected_cx = np.array([1., 5., 9.]) - elif comm.Get_rank()==1: - vtx_pl = np.array(np.array([2,4,8]), dtype=np.int32) - expected_cx = np.array([2.]) - - adapt_utils.remove_specified_vtx(zone_n, vtx_pl, comm) - - assert np.allclose(PT.get_value(PT.get_node_from_name(zone_n, 'CoordinateX')), expected_cx) - assert np.allclose(PT.get_value(PT.get_node_from_name(zone_n, 'cX')), expected_cx) - - new_distri = PT.maia.getDistribution(zone_n, 'Vertex')[1] - assert PT.Zone.n_vtx(zone_n)==4 - assert (maia.utils.par_utils.partial_to_full_distribution(new_distri, comm) == [0,3,4]).all() - - -@pytest_parallel.mark.parallel(2) -def test_elmt_pl_to_vtx_pl(comm): - zone = PT.new_Zone(type='Unstructured') - if comm.Get_rank() == 0: - vtx_distri = np.array([0, 5, 9], pdm_dtype) - econn = [1,2,5,4, 2,3,6,5] - distri = np.array([0, 2, 4], pdm_dtype) - elt_pl = np.array([1,3]) # Requested elts - elif comm.Get_rank() == 1: - vtx_distri = np.array([5, 9, 9], pdm_dtype) - econn = [4,5,8,7, 5,6,9,8] - distri = np.array([2, 4, 4], pdm_dtype) - elt_pl = np.array([], pdm_dtype) # Requested elts - elt = PT.new_Elements(type='QUAD_4', erange=[1,4], econn=econn, parent=zone) - PT.maia.newDistribution({'Element': distri}, elt) - PT.maia.newDistribution({'Vertex' : vtx_distri}, zone) - - quad_n = PT.get_child_from_predicate(zone, lambda n: PT.get_label(n)=='Elements_t' and\ - PT.Element.CGNSName(n)=='QUAD_4') - - vtx_pl = adapt_utils.elmt_pl_to_vtx_pl(zone, quad_n, elt_pl, comm) - if comm.Get_rank() == 0: - assert (vtx_pl == [1,2,4,5]).all() - elif comm.Get_rank() == 1: - assert (vtx_pl == [7,8]).all() - -@pytest_parallel.mark.parallel(2) -def test_tag_elmt_owning_vtx(comm): - zone = PT.new_Zone(type='Unstructured') - if comm.Get_rank() == 0: - econn = [1,2,5,4, 2,3,6,5] - distri = np.array([0, 2, 4], pdm_dtype) - vtx_pl = np.array([1,4,5]) #Requested vtx - elif comm.Get_rank() == 1: - econn = [4,5,8,7, 5,6,9,8] - distri = np.array([2, 4, 4], pdm_dtype) - vtx_pl = np.array([2]) #Requested vtx - elt = PT.new_Elements(type='QUAD_4', erange=[1,4], econn=econn, parent=zone) - PT.maia.newDistribution({'Element' : distri}, elt) - - quad_n = PT.get_child_from_predicate(zone, lambda n: PT.get_label(n)=='Elements_t' and\ - PT.Element.CGNSName(n)=='QUAD_4') - - elt_pl = adapt_utils.tag_elmt_owning_vtx(quad_n, vtx_pl, comm, elt_full=True) - assert (np.concatenate(comm.allgather(elt_pl)) == [1]).all() - elt_pl = adapt_utils.tag_elmt_owning_vtx(quad_n, vtx_pl, comm, elt_full=False) - assert (np.concatenate(comm.allgather(elt_pl)) == [1,2,3,4]).all() - - -@pytest_parallel.mark.parallel(2) -def test_convert_vtx_gcs_as_face_bcs(comm): - - rank = comm.Get_rank() - econn = np.array([1,2,3, 4,5,6], dtype=np.int32) if rank==0 else np.array([7,8,9], dtype=np.int32) - e_distri = [0,2,3] if rank==0 else [2,3,3] - bc_pl = np.array([[3]], dtype=np.int32) if rank==0 else np.array([[2]], dtype=np.int32) - bc_distri = [0,1,2] if rank==0 else [1,2,2] - gc0_pl = np.array([[5,7,9]], dtype=np.int32) if rank==0 else np.array([[4,6,8]], dtype=np.int32) - gc1_pl = np.array([[1]], dtype=np.int32) if rank==0 else np.array([[2,3]], dtype=np.int32) - - tree = PT.new_CGNSTree() - base = PT.new_CGNSBase(parent=tree) - zone_n = PT.new_Zone('zone', type='Unstructured', size=[[9,3,0]], parent=base) - elt_n = PT.new_Elements('TRI_3', type='TRI_3', erange=np.array([1,3], dtype=np.int32), econn=econn, parent=zone_n) - PT.maia.newDistribution({'Element':e_distri}, parent=elt_n) - zone_bc_n = PT.new_ZoneBC(parent=zone_n) - bc_n = PT.new_BC('bc0', point_list=bc_pl, loc='FaceCenter', parent=zone_bc_n) - PT.maia.newDistribution({'Index':bc_distri}, parent=bc_n) - zone_gc_n = PT.new_ZoneGridConnectivity(parent=zone_n) - gc_n = PT.new_GridConnectivity('gc0', type='Abutting1to1', point_list=gc0_pl, loc='Vertex', parent=zone_gc_n) - PT.new_GridConnectivityProperty(periodic={'translation': [1.0,0]}, parent=gc_n) - gc_n = PT.new_GridConnectivity('gc1', type='Abutting1to1', point_list=gc1_pl, loc='Vertex', parent=zone_gc_n) - PT.new_GridConnectivityProperty(periodic={'translation': [-1.0,0]}, parent=gc_n) - - adapt_utils.convert_vtx_gcs_as_face_bcs(tree, comm) - - # > All GCs are converted, empty GCs shouldn't be transformed - gc0 = PT.get_node_from_name_and_label(zone_n, 'gc0', 'BC_t') - gc1 = PT.get_node_from_name_and_label(zone_n, 'gc1', 'BC_t') - assert gc0 is None - assert gc1 is not None - if comm.rank==0: - assert np.array_equal(PT.get_child_from_name(gc1, 'PointList')[1], np.array([[1]], dtype=np.int32)) - if comm.rank==1: - assert np.array_equal(PT.get_child_from_name(gc1, 'PointList')[1], np.array([[]], dtype=np.int32)) - - -def test_apply_offset_to_elts(): - yt = f""" - Zone Zone_t: - BAR Elements_t I4 [3, 0]: - ElementRange IndexRange_t I4 [23, 30]: - TRI_1 Elements_t I4 [5, 0]: - ElementRange IndexRange_t I4 [1, 5]: - TETRA Elements_t I4 [10, 0]: - ElementRange IndexRange_t I4 [6, 8]: - TRI_2 Elements_t I4 [5, 0]: - ElementRange IndexRange_t I4 [11, 22]: - TRI_3 Elements_t I4 [5, 0]: - ElementRange IndexRange_t I4 [31, 31]: - ZoneBC ZoneBC_t: - BC1 BC_t 'Null': - GridLocation GridLocation_t 'CellCenter': - PointList IndexArray_t I4 [[6,7,8]]: - BC2 BC_t 'Null': - GridLocation GridLocation_t 'FaceCenter': - PointList IndexArray_t I4 [[1,2,11,22]]: - BC3 BC_t 'Null': - GridLocation GridLocation_t 'FaceCenter': - PointList IndexArray_t I4 [[1,3,5]]: - BC4 BC_t 'Null': - GridLocation GridLocation_t 'FaceCenter': - PointList IndexArray_t I4 [[23, 24, 25]]: - """ - zone = parse_yaml_cgns.to_node(yt) - elt_n = PT.get_child_from_name(zone, 'TETRA') - min_range = PT.Element.Range(elt_n)[1] - maia.algo.dist.adaptation_utils.apply_offset_to_elts(zone, -2, min_range) - - expected_elt_er = { 'TRI_1': np.array([1,5]), - 'TETRA': np.array([6,8]), - 'TRI_2': np.array([9,20]), - 'BAR' : np.array([21,28]), - 'TRI_3': np.array([29,29]), - } - for elt_name, elt_er in expected_elt_er.items(): - elt_n = PT.get_child_from_name(zone, elt_name) - assert np.array_equal(PT.Element.Range(elt_n), elt_er) - - expected_bc_pl = {'BC1': np.array([[6,7,8]]), - 'BC2': np.array([[1,2,9,20]]), - 'BC3': np.array([[1,3,5]]), - 'BC4': np.array([[21,22,23]]), - } - for bc_name, bc_pl in expected_bc_pl.items(): - bc_n = PT.get_node_from_name_and_label(zone, bc_name, 'BC_t') - assert np.array_equal(PT.Subset.getPatch(bc_n)[1], bc_pl) - - -@pytest_parallel.mark.parallel(2) -def test_is_elt_included(comm): - tree = maia.factory.generate_dist_block(3, 'TETRA_4', comm) - zone = PT.get_node_from_label(tree, 'Zone_t') - - # This is the selected TRI (57,58,59,60) : 1 10 4 13 4 10 7 4 16 13 16 4 - # This is the selected TETRA (2,3) : 11 10 14 2 13 14 10 4 - # Decomposed faces of TRI are : 11 10 14 11 14 2 11 10 2 10 14 2 - # 13 14 10 13 14 4 13 10 4 14 10 4 - # Only face TRI 58 appreas in tetra faces ^ Here - - if comm.Get_rank() == 0: - tri_pl = np.array([57,58]) - tetra_pl = np.array([2]) - else: - tri_pl = np.array([59, 60]) - tetra_pl = np.array([3]) - - tri_elt = PT.get_node_from_predicate(zone, lambda n : PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TRI_3') - tetra_elt = PT.get_node_from_predicate(zone, lambda n : PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TETRA_4') - out = adapt_utils.find_shared_faces(tri_elt, tri_pl, tetra_elt, tetra_pl, comm) - - if comm.Get_rank() == 0: - assert (out == [58]).all() - else: - assert (out == []).all() - -@pytest_parallel.mark.parallel([1,2,3]) -def test_add_undefined_faces(comm): - dist_tree = maia.factory.dcube_generator.dcube_nodal_generate(3, 1., [0.,0.,0.], 'TETRA_4', comm, get_ridges=True) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - - tet_n = PT.get_child_from_predicate(zone, lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TETRA_4') - tri_n = PT.get_child_from_predicate(zone, lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TRI_3') - - zmax_n = PT.get_node_from_path(zone, 'ZoneBC/Zmax') - zmax_pl = PT.Subset.getPatch(zmax_n)[1][0] - vtx_pl = adapt_utils.elmt_pl_to_vtx_pl(zone, tri_n, zmax_pl, comm) - cell_pl = adapt_utils.tag_elmt_owning_vtx(tet_n, vtx_pl, comm, elt_full=False) - - adapt_utils.add_undefined_faces(zone, tet_n, cell_pl, tri_n, comm) - - assert PT.Zone.n_vtx(zone)==27 - assert PT.Zone.n_cell(zone)==40 - assert np.array_equal(PT.Element.Range(tet_n),np.array([ 1,40], dtype=np.int32)) - assert PT.maia.getDistribution(tet_n, 'Element')[1][2]==40 - assert np.array_equal(PT.Element.Range(tri_n),np.array([41,96], dtype=np.int32)) - assert PT.maia.getDistribution(tri_n, 'Element')[1][2]==56 - - -@pytest_parallel.mark.parallel([1,3]) -def test_find_matching_bcs(comm): - - def distributed(array): - distri = par_utils.uniform_distribution(array.size, comm) - return array[distri[0]:distri[1]] - - distri_bar = par_utils.uniform_distribution(10, comm) - - bar_ec = np.array([1,2, 2,3, 4,1, 7,4, 3,6, 6,9, 101,102, 102,103, 104,101, 107,104])[distri_bar[0]*2:distri_bar[1]*2] - bc1_pl = distributed(np.array([1,2,4])) - bc2_pl = distributed(np.array([8,7,10])) - bc3_pl = distributed(np.array([1,4])) # Does not fully match with 8,7,10 - bc3_pl = distributed(np.array([5,6])) # Does not match at all - gc1_vtx = distributed(np.array([1,2,3,4,5,6,7,8,9], np.int32)) - gc2_vtx = distributed(np.array([101,102,103,104,105,106,107,108,109], np.int32)) - - zone_n = PT.new_Zone('zone', type='Unstructured', size=[[120,3,0]]) - PT.maia.newDistribution({'Vertex':par_utils.uniform_distribution(120, comm)}, parent=zone_n) - bar_n = PT.new_Elements('BAR_2', type='BAR_2', erange=[1,10], econn=bar_ec, parent=zone_n) - PT.maia.newDistribution({'Element':distri_bar}, parent=bar_n) - zbc_n = PT.new_ZoneBC(parent=zone_n) - PT.new_BC('BC1', point_list=bc1_pl.reshape((1,-1),order='F'), loc='EdgeCenter', parent=zbc_n) - PT.new_BC('BC2', point_list=bc2_pl.reshape((1,-1),order='F'), loc='EdgeCenter', parent=zbc_n) - PT.new_BC('BC3', point_list=bc3_pl.reshape((1,-1),order='F'), loc='EdgeCenter', parent=zbc_n) - - src_pl = distributed(np.array([1,2,3,4])) - tgt_pl = distributed(np.array([7,8,9,10])) - - matching_bcs = adapt_utils.find_matching_bcs(zone_n, bar_n, - src_pl, tgt_pl, - [gc1_vtx,gc2_vtx], - comm) - assert matching_bcs==[['BC2','BC1']] - - -@pytest.mark.parametrize('partial',[False, True]) -@pytest_parallel.mark.parallel([1,2,3]) -def test_update_elt_vtx_numbering(partial, comm): - distri_bar = par_utils.uniform_distribution(9, comm) - distri_vtx = par_utils.uniform_distribution(12, comm) - distri_num = par_utils.uniform_distribution(12, comm) - - bar_ec = np.array([5,6, 7,8, 9,10, 1,2, 9,10, 2,4, 4,3, 8,10, 11,12])[distri_bar[0]*2:distri_bar[1]*2] - - zone_n = PT.new_Zone('zone', type='Unstructured', size=[[12,3,0]]) - bar_n = PT.new_Elements('BAR_2', type='BAR_2', erange=np.array([1,6], dtype=np.int32), econn=bar_ec, parent=zone_n) - PT.maia.newDistribution({'Vertex' :distri_vtx}, parent=zone_n) - PT.maia.newDistribution({'Element':distri_bar}, parent=bar_n) - - old_to_new = np.array([1,6,3,5,5,6,7,8,9,8,9,12])[distri_num[0]:distri_num[1]] - - if partial: - distri_pl = par_utils.uniform_distribution(4, comm) - bar_pl = np.array([4,1,7,9])[distri_pl[0]:distri_pl[1]] - else: - bar_pl = None - - adapt_utils.update_elt_vtx_numbering(zone_n, bar_n, old_to_new, comm, bar_pl) - bar_ec = PT.get_value(PT.get_child_from_name(bar_n, 'ElementConnectivity')) - - if partial: - expected_ec = np.array([5,6, 7,8, 9,10, 1,6, 9,10, 2,4, 5,3, 8,10, 9,12])[distri_bar[0]*2:distri_bar[1]*2] - else: - expected_ec = np.array([5,6, 7,8, 9,8, 1,6, 9,8, 6,5, 5,3, 8,8, 9,12])[distri_bar[0]*2:distri_bar[1]*2] - - assert np.array_equal(bar_ec, expected_ec) diff --git a/maia/algo/dist/test/test_concat_nodes.py b/maia/algo/dist/test/test_concat_nodes.py deleted file mode 100644 index 69a629ef..00000000 --- a/maia/algo/dist/test/test_concat_nodes.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.pytree.yaml import parse_yaml_cgns -from maia.utils import par_utils -from maia.factory import full_to_dist as F2D - -from maia.algo.dist import concat_nodes as GN - -@pytest_parallel.mark.parallel([1,2]) -def test_concatenate_subset_nodes(comm): - yt = """ - BCa BC_t "BCFarfield": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[1, 2, 3, 4]]: - BCDataSet BCDataSet_t: - BCData BCData_t: - Data DataArray_t [10., 20., 30., 40.]: - BCb BC_t "BCFarfield": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[10, 20, 30, 40, 50, 60, 70, 80]]: - BCDataSet BCDataSet_t: - BCData BCData_t: - Data DataArray_t [1., 2., 3., 4., 5., 6., 7., 8.]: - """ - subset_nodes_f = parse_yaml_cgns.to_nodes(yt) - subset_nodes = [F2D.distribute_pl_node(node, comm) for node in subset_nodes_f] - - expected_distri = par_utils.uniform_distribution(4+8, comm) - if comm.Get_size() == 1: - expected_pl = [[1,2,3,4, 10,20,30,40,50,60,70,80]] - expected_data = [10,20,30,40, 1.,2.,3.,4.,5.,6.,7.,8] - elif comm.Get_size() == 2: - if comm.Get_rank() == 0: - expected_pl = [[1,2, 10,20,30,40]] - expected_data = [10,20, 1.,2.,3.,4] - elif comm.Get_rank() == 1: - expected_pl = [[3,4, 50,60,70,80]] - expected_data = [30,40, 5.,6.,7.,8] - - node = GN.concatenate_subset_nodes(subset_nodes, comm, output_name='BothBC', \ - additional_data_queries = ['BCDataSet/BCData/Data']) - assert PT.get_name(node) == 'BothBC' - assert PT.get_value(node) == 'BCFarfield' - assert PT.Subset.GridLocation(node) == 'FaceCenter' - assert (MT.getDistribution(node, 'Index')[1] == expected_distri).all() - assert (PT.get_child_from_name(node, 'PointList')[1][0] == expected_pl).all() - - assert PT.get_label(PT.get_node_from_path(node, 'BCDataSet/BCData')) == 'BCData_t' - assert (PT.get_node_from_name(node, 'Data')[1] == expected_data).all() - -@pytest_parallel.mark.parallel([1]) -@pytest.mark.parametrize("mode", ['', 'intrazone', 'periodic', 'intraperio']) -def test_concatenate_jns(comm, mode): - yt = """ - ZoneA Zone_t [[11, 10, 0]]: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - match1 GridConnectivity_t "ZoneB": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[1, 2, 3, 4]]: - PointListDonor IndexArray_t [[10, 20, 30, 40]]: - match2 GridConnectivity_t "ZoneB": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[5]]: - PointListDonor IndexArray_t [[50]]: - ZoneB Zone_t [[101, 100, 0]]: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - match3 GridConnectivity_t "ZoneA": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[10, 20, 30, 40]]: - PointListDonor IndexArray_t [[1, 2, 3, 4]]: - match4 GridConnectivity_t "ZoneA": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[50]]: - PointListDonor IndexArray_t [[5]]: - """ - tree = parse_yaml_cgns.to_cgns_tree(yt) - dist_tree = F2D.full_to_dist_tree(tree, comm) - zones = PT.get_all_Zone_t(dist_tree) - - if mode in ['intrazone', 'intraperio']: - zgc1 = PT.get_node_from_path(dist_tree, 'Base/ZoneA/ZGC') - for gc in PT.iter_children_from_label(zgc1, 'GridConnectivity_t'): - PT.set_value(gc, "ZoneA") - zgc2 = PT.get_node_from_path(dist_tree, 'Base/ZoneB/ZGC') - for gc in PT.iter_children_from_label(zgc2, 'GridConnectivity_t'): - PT.add_child(zgc1, gc) - PT.rm_nodes_from_name(dist_tree, PT.get_name(zones[1])) - - if mode in ['periodic', 'intraperio']: - for gc in PT.get_nodes_from_label(dist_tree, 'GridConnectivity_t')[:2]: - PT.new_GridConnectivityProperty({'rotation_angle': [45.,0.,0.]}, parent=gc) - for gc in PT.get_nodes_from_label(dist_tree, 'GridConnectivity_t')[2:]: - PT.new_GridConnectivityProperty({'rotation_angle': [-45.,0.,0.]}, parent=gc) - - GN.concatenate_jns(dist_tree, comm) - - gcs = PT.get_nodes_from_label(dist_tree, 'GridConnectivity_t') - opp_names = [PT.get_value(PT.get_child_from_name(gc, "GridConnectivityDonorName")) for gc in gcs] - assert len(gcs) == 2 - assert opp_names == [gc[0] for gc in gcs[::-1]] - - if mode=='intrazone': - assert all(['.I' in gc[0] for gc in gcs]) - if mode=='periodic': - assert all(['.P' in gc[0] for gc in gcs]) - assert len(PT.get_nodes_from_label(dist_tree, 'GridConnectivityProperty_t')) == 2 diff --git a/maia/algo/dist/test/test_conformize_jn.py b/maia/algo/dist/test/test_conformize_jn.py deleted file mode 100644 index 4b011efd..00000000 --- a/maia/algo/dist/test/test_conformize_jn.py +++ /dev/null @@ -1,54 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT - -import maia -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory import full_to_dist as F2D -from maia.factory import dcube_generator as DCG - -from maia.algo.dist import conformize_jn as CJN - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("from_loc", ["Vertex", "FaceCenter"]) -def test_conformize_jn_pair(comm, from_loc): - dist_tree = DCG.dcube_generate(5, 1., [0., 0., 0.], comm) - isize = 'I8' if maia.npy_pdm_gnum_dtype == np.int64 else 'I4' - # Add a fake join - if from_loc == "Vertex": - loc = 'Vertex' - pl = [[1,2,3,4,5]] - pld = [[125,124,123,122,121]] - if from_loc == "FaceCenter": - loc = "FaceCenter" - pl = [[1,2,3,4]] - pld = [[80,79,78,77]] - yt = f""" - matchA GridConnectivity_t "zone": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "{loc}": - PointList IndexArray_t {isize} {pl}: - PointListDonor IndexArray_t {isize} {pld}: - matchB GridConnectivity_t "zone": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "{loc}": - PointList IndexArray_t {isize} {pld}: - PointListDonor IndexArray_t {isize} {pl}: - """ - gcs = [F2D.distribute_pl_node(gc, comm) for gc in parse_yaml_cgns.to_nodes(yt)] - zone = PT.get_all_Zone_t(dist_tree)[0] - PT.new_child(zone, "ZGC", "ZoneGridConnectivity_t", children=gcs) - - CJN.conformize_jn_pair(dist_tree, ['Base/zone/ZGC/matchA', 'Base/zone/ZGC/matchB'], comm) - - if comm.rank == 0: - start, end = 0, 5 - elif comm.rank == 1: - start, end = 125-5, 125 - - assert (PT.get_node_from_name(zone, "CoordinateX")[1][start:end] == 0.5).all() - assert (PT.get_node_from_name(zone, "CoordinateY")[1][start:end] == 0.5).all() - assert (PT.get_node_from_name(zone, "CoordinateZ")[1][start:end] == 0.5).all() - diff --git a/maia/algo/dist/test/test_connect_match.py b/maia/algo/dist/test/test_connect_match.py deleted file mode 100644 index 1d95bfb5..00000000 --- a/maia/algo/dist/test/test_connect_match.py +++ /dev/null @@ -1,238 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import Pypdm.Pypdm as PDM - -import maia -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory.dcube_generator import dcube_generate, dcube_struct_generate - -from maia.utils import par_utils - -from maia.algo.dist import connect_match -from maia.algo.dist import redistribute -dtype = 'I4' if pdm_dtype == np.int32 else 'I8' - - -def test_shift_face_num(): - zone = parse_yaml_cgns.to_node(f""" - Zone Zone_t: - NGON Elements_t [22,0]: - ElementRange IndexRange_t [1, 25]: - """) - # NGON first - assert (connect_match._shift_face_num([4,6,10], zone) == [4,6,10]).all() - # NFace first - er = PT.get_node_from_name(zone, 'ElementRange')[1] - er[0] = 11 - assert (connect_match._shift_face_num([20,15], zone) == [10,5]).all() - assert (connect_match._shift_face_num([10,5], zone, True) == [20,15]).all() - - # Elements - zone = parse_yaml_cgns.to_node(f""" - Zone Zone_t: - Tetra Elements_t [10,0]: - ElementRange IndexRange_t [1, 20]: - Tri1 Elements_t [5,0]: - ElementRange IndexRange_t [21, 30]: - Tri2 Elements_t [5,0]: - ElementRange IndexRange_t [31, 40]: - """) - assert (connect_match._shift_face_num([38,22,40], zone) == [18,2,20]).all() - -def test_nodal_sections_to_face_vtx(): - sections = [ - {'pdm_type': PDM._PDM_MESH_NODAL_QUAD4, 'np_distrib': np.array([0, 2, 6]), 'np_connec': np.array([1,2,5,4, 2,3,6,5])}, - {'pdm_type': PDM._PDM_MESH_NODAL_TRIA3, 'np_distrib': np.array([0, 3, 6]), 'np_connec': np.array([6,2,4, 7,6,2, 6,5,3])} - ] - face_vtx_idx, face_vtx = connect_match._nodal_sections_to_face_vtx(sections, 0) - assert (face_vtx_idx == [0,4,8, 11, 14, 17]).all() - assert (face_vtx == [1,2,5,4, 2,3,6,5, 6,2,4, 7,6,2, 6,5,3]).all() - -@pytest_parallel.mark.parallel([1,3]) -@pytest.mark.parametrize("input_loc", ['Vertex', 'FaceCenter']) -@pytest.mark.parametrize("output_loc", ['Vertex', 'FaceCenter']) -def test_simple(input_loc, output_loc, comm): # __ - n_vtx = 3 # | | - dcubes = [dcube_generate(n_vtx, 1., [0,0,0], comm), # |__| - dcube_generate(n_vtx, 1., [0,0,1], comm)] # | | - zones = [PT.get_all_Zone_t(dcube)[0] for dcube in dcubes] # |__| - tree = PT.new_CGNSTree() - base = PT.new_CGNSBase(parent=tree) - for i_zone,zone in enumerate(zones): - zone[0] = f"zone{i_zone+1}" - PT.add_child(base, zone) - - zmax = PT.get_node_from_name(zones[0], 'Zmax') - PT.new_child(zmax, 'FamilyName', 'FamilyName_t', 'matchA') - zmin = PT.get_node_from_name(zones[1], 'Zmin') - PT.new_child(zmin, 'FamilyName', 'FamilyName_t', 'matchB') - - if input_loc == 'Vertex': - vtx_distri = par_utils.uniform_distribution(n_vtx**2, comm) - pl_zmin_f = np.arange(n_vtx**2, dtype=pdm_dtype) + 1 - pl_zmax_f = n_vtx**3 - np.arange(n_vtx**2, dtype=pdm_dtype) - for node, pl in zip([zmin, zmax], [pl_zmin_f, pl_zmax_f]): - PT.update_child(node, 'GridLocation', value='Vertex') - PT.update_child(node, 'PointList', value=pl[vtx_distri[0]:vtx_distri[1]].reshape((1,-1), order='F')) - MT.newDistribution({'Index' : vtx_distri}, node) - - connect_match.connect_1to1_families(tree, ('matchA', 'matchB'), comm, location=output_loc) - - assert len(PT.get_nodes_from_label(tree, 'BC_t')) == 10 - assert len(PT.get_nodes_from_label(tree, 'GridConnectivity_t')) == 2 - - if output_loc == 'FaceCenter': - expected_pl = np.array([[1,2,3,4]], pdm_dtype) - expected_pld = np.array([[9,10,11,12]], pdm_dtype) - elif output_loc == 'Vertex': - expected_pl = np.array([[1,2,3,4,5,6,7,8,9]], pdm_dtype) - expected_pld = np.array([[19,20,21,22,23,24,25,26,27]], pdm_dtype) - - # Redistribute and compare on rank 0 to avoid managing parallelism - expected_zmin_full = parse_yaml_cgns.to_node(f""" - Zmin_0 GridConnectivity_t "Base/zone1": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "{output_loc}": - GridConnectivityDonorName Descriptor_t "Zmax_0": - FamilyName FamilyName_t "matchB": - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [0,{expected_pl.size},{expected_pl.size}]: - """) - PT.new_child(expected_zmin_full, 'PointList', 'IndexArray_t', expected_pl) - PT.new_child(expected_zmin_full, 'PointListDonor', 'IndexArray_t', expected_pld) - - expected_zmax_full = parse_yaml_cgns.to_node(f""" - Zmax_0 GridConnectivity_t "Base/zone2": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "{output_loc}": - GridConnectivityDonorName Descriptor_t "Zmin_0": - FamilyName FamilyName_t "matchA": - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [0,{expected_pl.size},{expected_pl.size}]: - """) - PT.new_child(expected_zmax_full, 'PointList', 'IndexArray_t', expected_pld) - PT.new_child(expected_zmax_full, 'PointListDonor', 'IndexArray_t', expected_pl) - - gather_distri = lambda n_elt, comm : par_utils.gathering_distribution(0, n_elt, comm) - - zmin = PT.get_node_from_name(tree, 'Zmin_0') - zmax = PT.get_node_from_name(tree, 'Zmax_0') - redistribute.redistribute_pl_node(zmin, gather_distri, comm) - redistribute.redistribute_pl_node(zmax, gather_distri, comm) - - if comm.Get_rank() == 0: - assert PT.is_same_tree(zmin, expected_zmin_full) - assert PT.is_same_tree(zmax, expected_zmax_full) - - -@pytest_parallel.mark.parallel(2) -def test_partial_match(comm): # - n_vtx = 3 # __ - dcubes = [dcube_generate(n_vtx, 1., [0,0,0], comm), # __| | - dcube_generate(n_vtx, 1., [1,0,.5], comm)] # | |__| - zones = [PT.get_all_Zone_t(dcube)[0] for dcube in dcubes] # |__| - tree = PT.new_CGNSTree() # - base = PT.new_CGNSBase(parent=tree) - for i_zone,zone in enumerate(zones): - zone[0] = f"zone{i_zone+1}" - PT.add_child(base, zone) - - xmax = PT.get_node_from_name(zones[0], 'Xmax') - PT.new_child(xmax, 'FamilyName', 'FamilyName_t', 'matchA') - xmin = PT.get_node_from_name(zones[1], 'Xmin') - PT.new_child(xmin, 'FamilyName', 'FamilyName_t', 'matchB') - - connect_match.connect_1to1_families(tree, ('matchA', 'matchB'), comm) - - assert len(PT.get_nodes_from_label(tree, 'BC_t')) == 12 - assert len(PT.get_nodes_from_label(tree, 'GridConnectivity_t')) == 2 - - not_found_xmax = PT.get_node_from_name(zones[0], 'Xmax_unmatched') - not_found_xmin = PT.get_node_from_name(zones[1], 'Xmin_unmatched') - found_xmax = PT.get_node_from_name(zones[0], 'Xmax_0') - found_xmin = PT.get_node_from_name(zones[1], 'Xmin_0') - if comm.Get_rank() == 0: - assert (PT.get_child_from_name(not_found_xmax, 'PointList')[1] == [[21]]).all() - assert (PT.get_child_from_name( found_xmax, 'PointList')[1] == [[23]]).all() - assert (PT.get_child_from_name(not_found_xmin, 'PointList')[1] == [[15]]).all() - assert (PT.get_child_from_name( found_xmin, 'PointList')[1] == [[13]]).all() - elif comm.Get_rank() == 1: - assert (PT.get_child_from_name(not_found_xmax, 'PointList')[1] == [[22]]).all() - assert (PT.get_child_from_name( found_xmax, 'PointList')[1] == [[24]]).all() - assert (PT.get_child_from_name(not_found_xmin, 'PointList')[1] == [[16]]).all() - assert (PT.get_child_from_name( found_xmin, 'PointList')[1] == [[14]]).all() - - -@pytest_parallel.mark.parallel(1) -@pytest.mark.parametrize("out_loc", ['Vertex', 'FaceCenter']) -def test_multiple_match(out_loc, comm): - # Can not generate ngon with n_vtx != cst so convert it from S -_- - dcubes = [dcube_struct_generate([5,3,3], [2.,1,1], [0,0,0], comm), # __ __ - dcube_struct_generate(3, 1, [0,0,1], comm), # | | | - dcube_struct_generate(3, 1, [1,0,1], comm)] # |1_|2_| - zones = [PT.get_all_Zone_t(dcube)[0] for dcube in dcubes] # | | - tree = PT.new_CGNSTree() # |__0__| - base = PT.new_CGNSBase(parent=tree) # - for i_zone,zone in enumerate(zones): - zone[0] = f"zone{i_zone+1}" - PT.add_child(base, zone) - maia.algo.dist.convert_s_to_ngon(tree, comm) - - zones = PT.get_all_Zone_t(tree) - zmax = PT.get_node_from_name(zones[0], 'Zmax') - PT.new_child(zmax, 'FamilyName', 'FamilyName_t', 'matchA') - for zone in zones[1:]: - zmin = PT.get_node_from_name(zone, 'Zmin') - PT.new_child(zmin, 'FamilyName', 'FamilyName_t', 'matchB') - - connect_match.connect_1to1_families(tree, ('matchA', 'matchB'), comm, location=out_loc) - - assert len(PT.get_nodes_from_label(tree, 'BC_t')) == 15 - assert len(PT.get_nodes_from_label(tree, 'GridConnectivity_t')) == 4 - - if out_loc == 'FaceCenter': - assert (PT.get_node_from_predicates(zones[0], ['Zmax_0', 'PointList'])[1] == [[61,62,65,66]]).all() - assert (PT.get_node_from_predicates(zones[0], ['Zmax_1', 'PointList'])[1] == [[63,64,67,68]]).all() - assert (PT.get_node_from_predicates(zones[1], ['Zmin_0', 'PointList'])[1] == [[25,26,27,28]]).all() - assert (PT.get_node_from_predicates(zones[2], ['Zmin_0', 'PointList'])[1] == [[25,26,27,28]]).all() - elif out_loc == 'Vertex': - assert (PT.get_node_from_predicates(zones[0], ['Zmax_0', 'PointList'])[1] == [[31,32,33,36,37,38,41,42,43]]).all() - assert (PT.get_node_from_predicates(zones[0], ['Zmax_1', 'PointList'])[1] == [[33,34,35,38,39,40,43,44,45]]).all() - assert (PT.get_node_from_predicates(zones[1], ['Zmin_0', 'PointList'])[1] == [[1,2,3,4,5,6,7,8,9]]).all() - assert (PT.get_node_from_predicates(zones[2], ['Zmin_0', 'PointList'])[1] == [[1,2,3,4,5,6,7,8,9]]).all() - - - -@pytest.mark.parametrize("mesh_kind", ['Poly', 'HEXA_8']) -@pytest_parallel.mark.parallel(1) -def test_periodic_simple(mesh_kind, comm): # __ - # | | - # |__| - # - tree = maia.factory.generate_dist_block(3, mesh_kind, comm) - zone = PT.get_node_from_label(tree, 'Zone_t') - - xmin = PT.get_node_from_name(tree, 'Xmin') - PT.new_child(xmin, 'FamilyName', 'FamilyName_t', 'matchA') - xmax = PT.get_node_from_name(tree, 'Xmax') - PT.new_child(xmax, 'FamilyName', 'FamilyName_t', 'matchB') - - periodic = {'translation' : np.array([1.0, 0, 0], np.float32)} - connect_match.connect_1to1_families(tree, ('matchA', 'matchB'), comm, periodic=periodic) - - assert len(PT.get_nodes_from_label(tree, 'BC_t')) == 4 - assert len(PT.get_nodes_from_label(tree, 'GridConnectivity_t')) == 2 - - if mesh_kind == 'Poly': - assert (PT.get_node_from_predicates(zone, ['Xmin_0', 'PointList'])[1] == [[13,14,15,16]]).all() - assert (PT.get_node_from_predicates(zone, ['Xmax_0', 'PointList'])[1] == [[21,22,23,24]]).all() - elif mesh_kind == 'HEXA_8': - assert (PT.get_node_from_predicates(zone, ['Xmin_0', 'PointList'])[1] == [[17,18,19,20]]).all() - assert (PT.get_node_from_predicates(zone, ['Xmax_0', 'PointList'])[1] == [[21,22,23,24]]).all() - diff --git a/maia/algo/dist/test/test_convert_s_to_u.py b/maia/algo/dist/test/test_convert_s_to_u.py deleted file mode 100644 index c8b0fb06..00000000 --- a/maia/algo/dist/test/test_convert_s_to_u.py +++ /dev/null @@ -1,105 +0,0 @@ -import pytest -import pytest_parallel -import mpi4py.MPI as MPI -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.pytree.yaml import parse_yaml_cgns - -from maia.algo.dist import s_to_u - -############################################################################### -def test_n_face_per_dir(): - nVtx = np.array([7,9,5]) - assert (s_to_u.n_face_per_dir(nVtx, nVtx-1) == [7*8*4,6*9*4,6*8*5]).all() -############################################################################### - - -############################################################################### - -# --------------------------------------------------------------------------- # - -def test_bc_s_to_bc_u(): - #We dont test value of PL here, this is carried out by Test_compute_pointList_from_pointRanges - n_vtx = np.array([4,4,4]) - bc_s = PT.new_BC('MyBCName', type='BCOutflow', point_range=[[1,4], [1,4], [4,4]]) - bc_u = s_to_u.bc_s_to_bc_u(bc_s, n_vtx, 'FaceCenter', 0, 1) - assert PT.get_name(bc_u) == 'MyBCName' - assert PT.get_value(bc_u) == 'BCOutflow' - assert PT.Subset.GridLocation(bc_u) == 'FaceCenter' - assert PT.get_value(PT.get_child_from_name(bc_u, 'PointList')).shape == (1,9) - -def test_bcds_s_to_bcds_u(): - #We dont test value of PL here, this is carried out by Test_compute_pointList_from_pointRanges - n_vtx = np.array([4,4,4]) - bc_s = PT.new_BC('MyBCName', type='BCOutflow', point_range=[[1,4], [1,4], [4,4]]) - MT.newDistribution({'Index' : [0,16,16]}, parent=bc_s) - bcds = PT.new_child(bc_s, 'BCDataSet', 'BCDataSet_t') - bcdata = PT.new_child(bcds, "DirichletData", "BCData_t") - PT.new_DataArray("array", np.arange(16), parent=bcdata) - bc_u = s_to_u.bc_s_to_bc_u(bc_s, n_vtx, 'FaceCenter', 0, 1) - bcds = PT.get_node_from_name(bc_u, 'BCDataSet') - assert bcds is not None - assert PT.Subset.GridLocation(bcds) == 'Vertex' - assert PT.get_node_from_name(bcds, 'PointList') is not None - assert PT.get_node_from_name(bcds, 'array') is not None - -def test_gc_s_to_gc_u(): - #https://cgns.github.io/CGNS_docs_current/sids/cnct.html - #We dont test value of PL here, this is carried out by Test_compute_pointList_from_pointRanges - n_vtx_A = np.array([17,9,7]) - n_vtx_B = np.array([7,9,5]) - gcA_s = PT.new_GridConnectivity1to1('matchA', 'Base/zoneB', point_range=[[17,17], [3,9], [1,5]], \ - point_range_donor=[[7,1], [9,9], [5,1]], transform = [-2,-1,-3]) - gcB_s = PT.new_GridConnectivity1to1('matchB', 'zoneA', point_range=[[7,1], [9,9], [5,1]], \ - point_range_donor=[[17,17], [3,9], [1,5]], transform = [-2,-1,-3]) - - gcA_u = s_to_u.gc_s_to_gc_u(gcA_s, 'Base/zoneA', n_vtx_A, n_vtx_B, 'FaceCenter', 0, 1) - gcB_u = s_to_u.gc_s_to_gc_u(gcB_s, 'Base/zoneB', n_vtx_B, n_vtx_A, 'FaceCenter', 0, 1) - - assert PT.get_name(gcA_u) == 'matchA' - assert PT.get_name(gcB_u) == 'matchB' - assert PT.get_value(gcA_u) == 'Base/zoneB' - assert PT.get_value(gcB_u) == 'zoneA' - assert PT.Subset.GridLocation(gcA_u) == 'FaceCenter' - assert PT.Subset.GridLocation(gcB_u) == 'FaceCenter' - assert PT.get_value(PT.get_child_from_label(gcA_u, 'GridConnectivityType_t')) == 'Abutting1to1' - assert PT.get_value(PT.get_child_from_label(gcB_u, 'GridConnectivityType_t')) == 'Abutting1to1' - assert PT.get_value(PT.get_child_from_name(gcA_u, 'PointList')).shape == (1,24) - assert (PT.get_child_from_name(gcA_u, 'PointList')[1]\ - == PT.get_child_from_name(gcB_u, 'PointListDonor')[1]).all() - assert (PT.get_child_from_name(gcB_u, 'PointList')[1]\ - == PT.get_child_from_name(gcA_u, 'PointListDonor')[1]).all() - - gcA_s = PT.new_GridConnectivity1to1('matchB', 'Base/zoneA', point_range=[[17,17], [3,9], [1,5]], \ - point_range_donor=[[7,1], [9,9], [5,1]], transform = [-2,-1,-3]) - gcB_s = PT.new_GridConnectivity1to1('matchA', 'zoneB', point_range=[[7,1], [9,9], [5,1]], \ - point_range_donor=[[17,17], [3,9], [1,5]], transform = [-2,-1,-3]) - gcA_u = s_to_u.gc_s_to_gc_u(gcA_s, 'Base/zoneB', n_vtx_B, n_vtx_A, 'FaceCenter', 0, 1) - gcB_u = s_to_u.gc_s_to_gc_u(gcB_s, 'Base/zoneA', n_vtx_A, n_vtx_B, 'FaceCenter', 0, 1) - assert (PT.get_child_from_name(gcA_u, 'PointList')[1]\ - == PT.get_child_from_name(gcB_u, 'PointListDonor')[1]).all() - assert (PT.get_child_from_name(gcB_u, 'PointList')[1]\ - == PT.get_child_from_name(gcA_u, 'PointListDonor')[1]).all() - -@pytest_parallel.mark.parallel(2) -def test_zonedims_to_ngon(comm): - #We dont test value of faceVtx/ngon here, this is carried out by Test_compute_all_ngon_connectivity - n_vtx_zone = np.array([3,2,4]) - ngon = s_to_u.zonedims_to_ngon(n_vtx_zone, comm) - n_faces = PT.get_child_from_name(ngon, "ElementStartOffset")[1].shape[0] - 1 - if comm.Get_rank() == 0: - expected_n_faces = 15 - expected_eso = 4*np.arange(0,15+1) - elif comm.Get_rank() == 1: - expected_n_faces = 14 - expected_eso = 4*np.arange(15, 15+14+1) - assert n_faces == expected_n_faces - assert (PT.get_child_from_name(ngon, 'ElementRange')[1] == [1, 29]).all() - assert (PT.get_child_from_name(ngon, 'ElementStartOffset')[1] == expected_eso).all() - assert PT.get_node_from_path(ngon, ':CGNS#Distribution/ElementConnectivity')[1][2] == 4*29 - assert PT.get_child_from_name(ngon, 'ParentElements')[1].shape == (expected_n_faces, 2) - assert PT.get_child_from_name(ngon, 'ElementConnectivity')[1].shape == (4*expected_n_faces,) -############################################################################### diff --git a/maia/algo/dist/test/test_dist_transform.py b/maia/algo/dist/test/test_dist_transform.py deleted file mode 100644 index 83035e74..00000000 --- a/maia/algo/dist/test/test_dist_transform.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest -import pytest_parallel - -import numpy as np - -import maia -import maia.pytree as PT - -from maia.algo.dist import transform - -@pytest_parallel.mark.parallel(2) -def test_transform_affine_zone(comm): - tree = maia.factory.generate_dist_block(3, 'Poly', comm) - zone = PT.get_node_from_label(tree, 'Zone_t') - cx,cy,cz = PT.Zone.coordinates(zone) - PT.new_FlowSolution('FS', fields={'cx':cx, 'cy':cy, 'cz':cz}, parent=zone) - zone_bck = PT.deep_copy(zone) - - vtx_ids = np.array([], int) - transform.transform_affine_zone(zone, vtx_ids, comm, translation=[10, 0, 0]) - assert PT.is_same_tree(zone, zone_bck) - - if comm.Get_rank() == 0: - vtx_ids = np.array([3,21], int) - elif comm.Get_rank() == 1: - vtx_ids = np.array([21,1,18], int) - transform.transform_affine_zone(zone, vtx_ids, comm, translation=[10, 0, 0]) - - cx, cy, cz = PT.Zone.coordinates(zone) - cx_bck, cy_bck, cz_bck = PT.Zone.coordinates(zone_bck) - if comm.Get_rank() == 0: - assert (cx == cx_bck + np.array([10, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])).all() # 1 to 14 - elif comm.Get_rank() == 1: - assert (cx == cx_bck + np.array([0, 0, 0, 10, 0, 0, 10, 0, 0, 0, 0, 0, 0])).all() # 15 to 27 - assert (cy == cy_bck).all() and (cz == cz_bck).all() - assert (PT.get_node_from_name(zone, 'cx')[1] == cx).all() - diff --git a/maia/algo/dist/test/test_dngon_tools.py b/maia/algo/dist/test/test_dngon_tools.py deleted file mode 100644 index 69ac90c7..00000000 --- a/maia/algo/dist/test/test_dngon_tools.py +++ /dev/null @@ -1,56 +0,0 @@ -import pytest_parallel - -import numpy as np - -import maia.pytree as PT - -from maia.factory import dcube_generator as DCG -from maia.factory import full_to_dist as F2D -from maia.algo.dist import ngon_tools as NGT - -@pytest_parallel.mark.parallel([1,3]) -def test_pe_to_nface(comm): - # 1. Create test input - tree = DCG.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_node_from_label(tree, 'Zone_t') - dtype = PT.get_node_from_name(zone, 'ParentElements')[1].dtype - - # 2. Creating expected values - nface_er_exp = np.array([37,44], dtype) - nface_eso_exp = np.array([0, 6, 12, 18, 24, 30, 36, 42, 48], dtype) - nface_ec_exp = np.array([1,5,13,17,25,29, -17,2,6,21,27,31, - -29,3,7,14,18,33, -31,-18,4,8,22,35, - -5,9,15,19,26,30, -19,-6,10,23,28,32, - -30,-7,11,16,20,34, -32,-20,-8,12,24,36], dtype) - nface_exp_f = PT.new_NFaceElements('NFaceElements', erange=nface_er_exp, eso=nface_eso_exp, ec=nface_ec_exp) - nface_exp = F2D.distribute_element_node(nface_exp_f, comm) - - # 3. Tested function - NGT.pe_to_nface(zone, comm, True) - - # 4. Check results - nface = PT.Zone.NFaceNode(zone) - assert PT.is_same_tree(nface, nface_exp) - assert PT.get_node_from_name(zone, "ParentElements") is None - -@pytest_parallel.mark.parallel([1,3]) -def test_nface_to_pe(comm): - # 1. Create test input - tree = DCG.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_node_from_label(tree, 'Zone_t') - pe_bck = PT.get_node_from_path(zone, 'NGonElements/ParentElements')[1] - - NGT.pe_to_nface(zone, comm, True) - nface_bck = PT.get_node_from_name(zone, 'NFaceElements') - - # 2. Tested function - rmNface = (comm.size != 3) - NGT.nface_to_pe(zone, comm, rmNface) - - # 3. Check results - assert (PT.get_node_from_path(zone, 'NGonElements/ParentElements')[1] == pe_bck).all() - nface_cur = PT.get_node_from_name(zone, 'NFaceElements') - if rmNface: - assert nface_cur is None - else: - assert PT.is_same_tree(nface_bck, nface_cur) diff --git a/maia/algo/dist/test/test_duplicate.py b/maia/algo/dist/test/test_duplicate.py deleted file mode 100644 index 5b4c7883..00000000 --- a/maia/algo/dist/test/test_duplicate.py +++ /dev/null @@ -1,194 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np -import os - -import maia -import maia.pytree as PT - -from maia.io import file_to_dist_tree -from maia.utils import test_utils as TU - -from maia.algo.dist import duplicate - -############################################################################### -@pytest_parallel.mark.parallel([1,3]) -def test_duplicate_from_periodic_jns(comm): - - yaml_path = os.path.join(TU.sample_mesh_dir, 'quarter_crown_square_8.yaml') - dist_tree = file_to_dist_tree(yaml_path, comm) - - match_perio_by_trans_a = 'Base/Zone/ZoneGridConnectivity/MatchTranslationA' - match_perio_by_trans_b = 'Base/Zone/ZoneGridConnectivity/MatchTranslationB' - jn_paths_for_dupl = [[match_perio_by_trans_a],[match_perio_by_trans_b]] - - zone_basename = PT.get_name(PT.get_all_Zone_t(dist_tree)[0]) - zone_paths = ['Base/' + PT.get_name(zone) for zone in PT.get_all_Zone_t(dist_tree)] - - duplicate.duplicate_from_periodic_jns(dist_tree, zone_paths, jn_paths_for_dupl, 1, comm) - - assert len(PT.get_all_Zone_t(dist_tree)) == 2 - - zone0, zone1 = PT.get_all_Zone_t(dist_tree) - assert (PT.get_name(zone0) == zone_basename+".D0") and (PT.get_name(zone1) == zone_basename+".D1") - - coord0 = PT.Zone.coordinates(zone0) - coord1 = PT.Zone.coordinates(zone1) - assert np.allclose(coord0[0], coord1[0] ) - assert np.allclose(coord0[1], coord1[1] ) - assert np.allclose(coord0[2], coord1[2]+2.) - - zgc0 = PT.get_child_from_label(zone0, "ZoneGridConnectivity_t") - zgc1 = PT.get_child_from_label(zone1, "ZoneGridConnectivity_t") - for gc0 in PT.get_children_from_label(zgc0, "GridConnectivity_t"): - gc0_name = PT.get_name(gc0) - gc1 = PT.get_child_from_name_and_label(zgc1, gc0_name, "GridConnectivity_t") - oppname0 = PT.get_value(PT.get_child_from_name(gc0, "GridConnectivityDonorName")) - oppname1 = PT.get_value(PT.get_child_from_name(gc1, "GridConnectivityDonorName")) - assert oppname0 == oppname1 - if gc0_name in ["MatchRotationA","MatchRotationB"]: #Joins by rotation - assert PT.get_value(gc0) == zone_basename+".D0" - assert PT.get_value(gc1) == zone_basename+".D1" - rotation_center0, rotation_angle0, translation0 = PT.GridConnectivity.periodic_values(gc0) - rotation_center1, rotation_angle1, translation1 = PT.GridConnectivity.periodic_values(gc1) - assert (rotation_center0 == rotation_center1).all() - assert (rotation_angle0 == rotation_angle1).all() - assert (translation0 == translation1).all() - elif gc0_name in ["MatchTranslationA","MatchTranslationB"]: #Joins by translation - assert PT.get_value(gc0) == zone_basename+".D1" - assert PT.get_value(gc1) == zone_basename+".D0" - if gc0_name == "MatchTranslationA": #Join0 => perio*2 and join1 => not perio - gcp1 = PT.get_child_from_label(gc1, "GridConnectivityProperty_t") - assert gcp1 is None - rotation_center0, rotation_angle0, translation0 = PT.GridConnectivity.periodic_values(gc0) - assert (rotation_center0 == np.zeros(3)).all() - assert (rotation_angle0 == np.zeros(3)).all() - assert (translation0 == np.array([0.0, 0.0, -2.0])*2).all() - else: #Join0 => not perio and join1 => perio*2 - gcp0 = PT.get_child_from_label(gc0, "GridConnectivityProperty_t") - assert gcp0 is None - rotation_center1, rotation_angle1, translation1 = PT.GridConnectivity.periodic_values(gc1) - assert (rotation_center1 == np.zeros(3)).all() - assert (rotation_angle1 == np.zeros(3)).all() - assert (translation1 == np.array([0.0, 0.0, 2.0])*2).all() - else: - assert False - -############################################################################### - -############################################################################### -@pytest_parallel.mark.parallel(2) -def test_duplicate_zones_from_periodic_join_by_rotation_to_360(comm): - - yaml_path = os.path.join(TU.sample_mesh_dir, 'quarter_crown_square_8.yaml') - dist_tree = file_to_dist_tree(yaml_path,comm) - - match_perio_by_rot_a = 'Base/Zone/ZoneGridConnectivity/MatchRotationA' - match_perio_by_rot_b = 'Base/Zone/ZoneGridConnectivity/MatchRotationB' - jn_paths_for_dupl = [[match_perio_by_rot_a],[match_perio_by_rot_b]] - - zone_basename = PT.get_name(PT.get_all_Zone_t(dist_tree)[0]) - zone_paths = ['Base/' + PT.get_name(zone) for zone in PT.get_all_Zone_t(dist_tree)] - - duplicate.duplicate_from_rotation_jns_to_360(dist_tree, zone_paths, - jn_paths_for_dupl, comm, conformize=True) - - zones = PT.get_all_Zone_t(dist_tree) - assert len(zones) == 4 - assert all([zone[0] == f"{zone_basename}.D{i}" for i, zone in enumerate(zones)]) - - coord0, coord1, coord2, coord3 = [PT.Zone.coordinates(zone) for zone in zones] - assert np.allclose(coord0[0], -coord2[0]) - assert np.allclose(coord0[1], -coord2[1]) - assert np.allclose(coord0[2], coord2[2]) - assert np.allclose(coord1[0], -coord3[0]) - assert np.allclose(coord1[1], -coord3[1]) - assert np.allclose(coord1[2], coord3[2]) - assert np.allclose(coord0[0], -coord1[1]) - assert np.allclose(coord0[1], coord1[0]) - assert np.allclose(coord0[2], coord1[2]) - assert np.allclose(coord0[0], coord3[1]) - assert np.allclose(coord0[1], -coord3[0]) - assert np.allclose(coord0[2], coord3[2]) - - zgc0 = PT.get_child_from_label(zones[0], "ZoneGridConnectivity_t") - zgc1 = PT.get_child_from_label(zones[1], "ZoneGridConnectivity_t") - zgc2 = PT.get_child_from_label(zones[2], "ZoneGridConnectivity_t") - zgc3 = PT.get_child_from_label(zones[3], "ZoneGridConnectivity_t") - for gc0 in PT.get_children_from_label(zgc0, "GridConnectivity_t"): - gc0_name = PT.get_name(gc0) - gc1 = PT.get_child_from_name_and_label(zgc1, gc0_name, "GridConnectivity_t") - gc2 = PT.get_child_from_name_and_label(zgc2, gc0_name, "GridConnectivity_t") - gc3 = PT.get_child_from_name_and_label(zgc3, gc0_name, "GridConnectivity_t") - oppname0 = PT.get_value(PT.get_child_from_name(gc0, "GridConnectivityDonorName")) - oppname1 = PT.get_value(PT.get_child_from_name(gc1, "GridConnectivityDonorName")) - oppname2 = PT.get_value(PT.get_child_from_name(gc1, "GridConnectivityDonorName")) - oppname3 = PT.get_value(PT.get_child_from_name(gc1, "GridConnectivityDonorName")) - assert oppname0 == oppname1 == oppname2 == oppname3 - if gc0_name in ["MatchRotationA","MatchRotationB"]: #Joins by rotation => not perio - if gc0_name == "MatchRotationA": - assert PT.get_value(gc0) == zone_basename+".D3" - assert PT.get_value(gc1) == zone_basename+".D0" - assert PT.get_value(gc2) == zone_basename+".D1" - assert PT.get_value(gc3) == zone_basename+".D2" - else: - assert PT.get_value(gc0) == zone_basename+".D1" - assert PT.get_value(gc1) == zone_basename+".D2" - assert PT.get_value(gc2) == zone_basename+".D3" - assert PT.get_value(gc3) == zone_basename+".D0" - assert PT.get_child_from_label(gc0, "GridConnectivityProperty_t") is None - assert PT.get_child_from_label(gc1, "GridConnectivityProperty_t") is None - assert PT.get_child_from_label(gc2, "GridConnectivityProperty_t") is None - assert PT.get_child_from_label(gc3, "GridConnectivityProperty_t") is None - elif gc0_name in ["MatchTranslationA","MatchTranslationB"]: #Joins by translation => no change execpt value - assert PT.get_value(gc0) == zone_basename+".D0" - assert PT.get_value(gc1) == zone_basename+".D1" - assert PT.get_value(gc2) == zone_basename+".D2" - assert PT.get_value(gc3) == zone_basename+".D3" - pass - else: - assert False - - -@pytest_parallel.mark.parallel(1) -def test_duplicate_2d(comm): - - # Prepare 2D periodic case - dist_tree = maia.factory.generate_dist_block([5,2,1], 'TRI_3', comm, origin=[.5, -0.5]) - maia.algo.scale_mesh(dist_tree, [4,1]) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - cx = PT.get_node_from_name(zone, 'CoordinateX') - cy = PT.get_node_from_name(zone, 'CoordinateY') - cy[1] = np.sign(cy[1]) * cx[1] - - ymin = PT.get_node_from_name(zone, 'Ymin') - ymax = PT.get_node_from_name(zone, 'Ymax') - PT.rm_nodes_from_name(zone, 'Ym*') - - zgc = PT.new_ZoneGridConnectivity(parent=zone) - gc = PT.new_GridConnectivity('Bottom', zone[0], 'Abutting1to1', loc='EdgeCenter', \ - point_list = PT.get_child_from_name(ymin, 'PointList')[1], - point_list_donor = PT.get_child_from_name(ymax, 'PointList')[1], - parent=zgc) - PT.add_child(gc, PT.get_child_from_name(ymin, ':CGNS#Distribution')) - PT.new_GridConnectivityProperty(periodic={'rotation_angle' : [np.pi/2, 0], 'rotation_center' : [0,0], 'translation' : [0,0]}, - parent=gc) - - gc = PT.new_GridConnectivity('Top', zone[0], 'Abutting1to1', loc='EdgeCenter', \ - point_list = PT.get_child_from_name(ymax, 'PointList')[1], - point_list_donor = PT.get_child_from_name(ymin, 'PointList')[1], - parent=zgc) - PT.new_GridConnectivityProperty(periodic={'rotation_angle' : [-np.pi/2, 0], 'rotation_center' : [0,0], 'translation' : [0,0]}, - parent=gc) - PT.add_child(gc, PT.get_child_from_name(ymax, ':CGNS#Distribution')) - - - - # Run test - match_perio_by_rot_a = 'Base/zone/ZoneGridConnectivity/Bottom' - match_perio_by_rot_b = 'Base/zone/ZoneGridConnectivity/Top' - jn_paths_for_dupl = [[match_perio_by_rot_a],[match_perio_by_rot_b]] - - duplicate.duplicate_from_rotation_jns_to_360(dist_tree, ['Base/zone'], jn_paths_for_dupl, comm) - - assert len(PT.get_nodes_from_label(dist_tree, 'Zone_t')) == 4 \ No newline at end of file diff --git a/maia/algo/dist/test/test_extract_surf_dmesh.py b/maia/algo/dist/test/test_extract_surf_dmesh.py deleted file mode 100644 index f04a7da1..00000000 --- a/maia/algo/dist/test/test_extract_surf_dmesh.py +++ /dev/null @@ -1,53 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory import dcube_generator -from maia.factory import full_to_dist as F2D - -from maia.algo.dist import extract_surf_dmesh as EXC - -@pytest_parallel.mark.parallel(2) -def test_extract_surf_zone(comm): - tree = dcube_generator.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - - #Simplify mesh keeping only 2 BCs - zone_bc = PT.get_child_from_label(zone, "ZoneBC_t") - zone_bc[2] = [zone_bc[2][0], zone_bc[2][3]] - - - surf_zone = EXC.extract_surf_zone_from_queries(zone, [['ZoneBC_t', 'BC_t']], comm) - - assert PT.Zone.CellSize(surf_zone) == 2*2*2 #2BC, 2*2 faces - - dtype = 'I4' if pdm_dtype == np.int32 else 'I8' - yt = f""" - NGonElements Elements_t [22,0]: - ElementRange IndexRange_t [1,8]: - ElementStartOffset DataArray_t {dtype} [0,4,8,12,16,20,24,28,32]: - ElementConnectivity DataArray_t: - {dtype} : [2,5,4,1,3,6,5,2,5,8,7,4,6,9,8,5,10,11,6,3,11,12,9,6,13,14,11,10,14,15,12,11] - """ - expected_ngon_full = parse_yaml_cgns.to_node(yt) - expected_ngon = F2D.distribute_element_node(expected_ngon_full, comm) - - ngon = PT.Zone.NGonNode(surf_zone) - assert PT.is_same_tree(ngon, expected_ngon) - - -@pytest_parallel.mark.parallel(3) -def test_extract_surf_tree(comm): - tree = dcube_generator.dcube_generate(4,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - - surf_zone = EXC.extract_surf_zone_from_queries(zone, [['ZoneBC_t', 'BC_t']], comm) - surf_tree = EXC.extract_surf_tree_from_bc(tree, comm) - - assert len(PT.get_all_CGNSBase_t(surf_tree)) == 1 - assert len(PT.get_all_Zone_t(surf_tree)) == 1 - assert PT.is_same_tree(PT.get_all_Zone_t(surf_tree)[0], surf_zone) diff --git a/maia/algo/dist/test/test_generate_ngon_from_std_elements.py b/maia/algo/dist/test/test_generate_ngon_from_std_elements.py deleted file mode 100644 index 33d195ae..00000000 --- a/maia/algo/dist/test/test_generate_ngon_from_std_elements.py +++ /dev/null @@ -1,167 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.algo.dist import ngon_from_std_elements as GNG - -def test_raise_if_possible_overflow(): - GNG.raise_if_possible_overflow(2000000000,1) #OK - GNG.raise_if_possible_overflow(8000000000,4) #OK - with pytest.raises(OverflowError): - GNG.raise_if_possible_overflow(8000000000,2) #Overflow - -@pytest_parallel.mark.parallel(2) -class Test_compute_ngon_from_std_elements: - - def test_3d_mesh(self, comm): - #Generated from G.cartTetra((0,0,0), (1./3, 1./2, 0), (3,3,2)) - rank = comm.Get_rank() - if comm.Get_rank() == 0: - tetra_ec = [1,2,4,10,2,5,4,14,4,10,14,13,2,10,11,14,2,4,10,14,2,6,5,14,2,12,3,6,14,15,12,6,12,14,2,11,12,2,14,6] - elif comm.Get_rank() == 1: - tetra_ec = [4,8,7,16,4,14,5,8,16,17,14,8,14,16,4,13,14,4,16,8,5,6,8,14,6,9,8,18,8,14,18,17,6,14,15,18,6,8,14,18] - tetra_ec = np.array(tetra_ec, pdm_dtype) - - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=3, phy_dim=3, parent=dist_tree) - dist_zone = PT.new_Zone('Zone', size=[[0,20,0]], type='Unstructured', parent=dist_base) - MT.newDistribution({'Vertex' : [9*rank,9*(rank+1),18], 'Cell' : [10*rank, 10*(rank+1), 20]}, dist_zone) - tetra = PT.new_Elements('Tetra', 'TETRA_4', erange=[1,20], econn=tetra_ec, parent=dist_zone) - MT.newDistribution({'Element' : np.array([10*rank,10*(rank+1),20], pdm_dtype)}, tetra) - - GNG.generate_ngon_from_std_elements(dist_tree, comm) - - assert PT.get_node_from_path(dist_tree, 'Base/Zone/Tetra') is None - ngon = PT.get_node_from_path(dist_tree, 'Base/Zone/NGonElements') - nface = PT.get_node_from_path(dist_tree, 'Base/Zone/NFaceElements') - - assert (PT.Element.Range(ngon) == [1,56]).all() - assert (PT.Element.Range(nface) == [57,76]).all() - if rank == 0: - assert (PT.get_value(MT.getDistribution(ngon, 'Element')) == [0,27,56]).all() - assert (PT.get_value(MT.getDistribution(ngon, 'ElementConnectivity')) == [0,81,168]).all() - assert (PT.get_value(MT.getDistribution(nface, 'Element')) == [0,10,20]).all() - assert (PT.get_child_from_name(ngon, 'ElementConnectivity')[1][:6] == [1,4,2,2,4,5]).all() - assert (PT.get_child_from_name(ngon, 'ParentElements')[1][8:12] == [[68,0], [67,0], [72,0], [58,61]]).all() - elif rank == 1: - assert (PT.get_value(MT.getDistribution(ngon, 'Element')) == [27,56,56]).all() - assert (PT.get_value(MT.getDistribution(ngon, 'ElementConnectivity')) == [81,168,168]).all() - assert (PT.get_value(MT.getDistribution(nface, 'Element')) == [10,20,20]).all() - assert (PT.get_child_from_name(ngon, 'ElementConnectivity')[1][-6:] == [14,15,18,14,18,17]).all() - assert (PT.get_child_from_name(ngon, 'ParentElements')[1][4:8] == [[59,70], [67,0], [64,66], [73,76]]).all() - assert (PT.get_child_from_name(nface, 'ElementStartOffset')[1] == np.arange(0,44,4)+40*rank).all() - - def test_2d_mesh(self, comm): - #Generated from G.cartHexa((0,0,0), (1./3, 1./2, 0), (4,3,1)) - rank = comm.Get_rank() - if comm.Get_rank() == 0: - quad_ec = np.array([1,2,6,5,2,3,7,6,3,4,8,7], pdm_dtype) - elif comm.Get_rank() == 1: - quad_ec = np.array([5,6,10,9,6,7,11,10,7,8,12,11], pdm_dtype) - - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=2, phy_dim=3, parent=dist_tree) - dist_zone = PT.new_Zone('Zone', size=[[12,6,0]], type='Unstructured', parent=dist_base) - MT.newDistribution({'Vertex' : np.array([6*rank,6*(rank+1),12], pdm_dtype), - 'Cell' : np.array([3*rank, 3*(rank+1), 6], pdm_dtype)}, dist_zone) - quad = PT.new_Elements('Quad', 'QUAD_4', erange=[1,6], econn=quad_ec, parent=dist_zone) - MT.newDistribution({'Element' : np.array([3*rank,3*(rank+1),6], pdm_dtype)}, quad) - - GNG.generate_ngon_from_std_elements(dist_tree, comm) - - assert PT.get_node_from_path(dist_tree, 'Base/Zone/Quad') is None - edge = PT.get_node_from_path(dist_tree, 'Base/Zone/EdgeElements') - ngon = PT.get_node_from_path(dist_tree, 'Base/Zone/NGonElements') - assert PT.get_node_from_path(dist_tree, 'Base/Zone/NFaceElements') is None - - assert (PT.Element.Range(edge) == [1,17]).all() - assert (PT.Element.Range(ngon) == [18,23]).all() - if rank == 0: - assert (PT.get_value(MT.getDistribution(edge, 'Element')) == [0,9,17]).all() - assert (PT.get_value(MT.getDistribution(ngon, 'Element')) == [0,3,6]).all() - assert (PT.get_value(MT.getDistribution(ngon, 'ElementConnectivity')) == [0,12,24]).all() - - assert (PT.get_child_from_name(edge, 'ElementConnectivity')[1] == [1,2,2,3,5,1,3,4,2,6,3,7,6,5,4,8,7,6]).all() - assert (PT.get_child_from_name(ngon, 'ElementStartOffset')[1] == [0,4,8,12]).all() - assert (PT.get_child_from_name(ngon, 'ElementConnectivity')[1] == [1,2,6,5, 6,2,3,7, 7,3,4,8]).all() - elif rank == 1: - assert (PT.get_value(MT.getDistribution(edge, 'Element')) == [9,17,17]).all() - assert (PT.get_value(MT.getDistribution(ngon, 'Element')) == [3,6,6]).all() - assert (PT.get_value(MT.getDistribution(ngon, 'ElementConnectivity')) == [12,24,24]).all() - - assert (PT.get_child_from_name(edge, 'ElementConnectivity')[1] == [9,5,8,7,6,10,7,11,10,9,8,12,11,10,12,11]).all() - assert (PT.get_child_from_name(ngon, 'ElementStartOffset')[1] == [12,16,20,24]).all() - assert (PT.get_child_from_name(ngon, 'ElementConnectivity')[1] == [5,6,10,9, 10,6,7,11, 11,7,8,12]).all() - - def test_2d_mesh_with_bc(self, comm): - rank = comm.Get_rank() - if comm.Get_rank() == 0: - quad_ec = np.array([1,2,6,5,2,3,7,6,3,4,8,7], pdm_dtype) - bar_ec = np.array([10,9,11,10,12,11,5,1,9,5], pdm_dtype) - elif comm.Get_rank() == 1: - quad_ec = np.array([5,6,10,9,6,7,11,10,7,8,12,11], pdm_dtype) - bar_ec = np.empty(0, dtype=pdm_dtype) - - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=2, phy_dim=3, parent=dist_tree) - dist_zone = PT.new_Zone('Zone', size=[[12,6,0]], type='Unstructured', parent=dist_base) - MT.newDistribution({'Vertex' : [6*rank,6*(rank+1),12], 'Cell' : [3*rank, 3*(rank+1), 6]}, dist_zone) - quad = PT.new_Elements('Quad', 'QUAD_4', erange=[1,6], econn=quad_ec, parent=dist_zone) - MT.newDistribution({'Element' : np.array([3*rank,3*(rank+1),6], pdm_dtype)}, quad) - bar = PT.new_Elements('Bar', 'BAR_2', erange=[7,11], econn=bar_ec, parent=dist_zone) - MT.newDistribution({'Element' : np.array([0,5*(1-rank),5], pdm_dtype)}, bar) - dist_zbc = PT.new_ZoneBC(dist_zone) - if rank == 0: - bca = PT.new_BC('bcA', point_range = [[7,9]], parent=dist_zbc) - MT.newDistribution({'Index' : [0,2,3]}, bca) - bcb = PT.new_BC('bcB', point_list = [[10]], parent=dist_zbc) - MT.newDistribution({'Index' : [0,1,2]}, bcb) - bcc = PT.new_BC('bcC', point_range = [[1,5]], parent=dist_zbc) - MT.newDistribution({'Index' : [0,3,5]}, bcb) - elif rank == 1: - bca = PT.new_BC('bcA', point_range = [[7,9]], parent=dist_zbc) - MT.newDistribution({'Index' : [2,3,3]}, bca) - bcb = PT.new_BC('bcB', point_list = [[11]], parent=dist_zbc) - MT.newDistribution({'Index' : [1,2,2]}, bcb) - bcc = PT.new_BC('bcC', point_range = [[1,5]], parent=dist_zbc) - MT.newDistribution({'Index' : [3,5,5]}, bcb) - PT.new_GridLocation('EdgeCenter', bca) - PT.new_GridLocation('EdgeCenter', bcb) - PT.new_GridLocation('Vertex', bcc) - - GNG.generate_ngon_from_std_elements(dist_tree, comm) - - assert PT.get_node_from_path(dist_tree, 'Base/Zone/Quad') is None - assert PT.get_node_from_path(dist_tree, 'Base/Zone/Bar') is None - ngon = PT.get_node_from_path(dist_tree, 'Base/Zone/NGonElements') - edge = PT.get_node_from_path(dist_tree, 'Base/Zone/EdgeElements') - - #Apparently addition elements causes the distribution to change // dont retest here - assert (PT.Element.Range(edge) == [1,17]).all() - assert (PT.Element.Range(ngon) == [18,23]).all() - assert PT.Subset.GridLocation(bca) == 'EdgeCenter' - if rank == 0: - assert (PT.get_child_from_name(bca, 'PointList')[1] == [14,16]).all() - assert (PT.get_child_from_name(bcb, 'PointList')[1] == [3]).all() - elif rank == 1: - assert (PT.get_child_from_name(bca, 'PointList')[1] == [17]).all() - assert (PT.get_child_from_name(bcb, 'PointList')[1] == [10]).all() - assert PT.Subset.GridLocation(bcc) == 'Vertex' #Vertex bc should not have changed - assert (PT.get_child_from_name(bcc, 'PointRange')[1] == [[1,5]]).all() - -@pytest_parallel.mark.parallel(2) -def test_from_mixed(comm): - tree_elt = maia.factory.generate_dist_block(3, 'TETRA_4', comm) - tree_mix = PT.deep_copy(tree_elt) - maia.algo.dist.convert_elements_to_mixed(tree_mix, comm) - - GNG.convert_elements_to_ngon(tree_elt, comm) - GNG.convert_elements_to_ngon(tree_mix, comm) - - assert PT.is_same_tree(tree_elt, tree_mix) - diff --git a/maia/algo/dist/test/test_geometry.py b/maia/algo/dist/test/test_geometry.py deleted file mode 100644 index 3d4402c6..00000000 --- a/maia/algo/dist/test/test_geometry.py +++ /dev/null @@ -1,99 +0,0 @@ -import pytest -import pytest_parallel -from mpi4py import MPI -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia - -from maia.algo.dist import geometry - -@pytest_parallel.mark.parallel(3) -def test_compute_face_normal3d(comm): - tree = maia.factory.generate_dist_block(3, 'Poly', comm) - zone = PT.get_all_Zone_t(tree)[0] - - face_normal = geometry.compute_face_normal(zone, comm) - - # All face area are 0.25 - if comm.Get_rank() == 0: - expected_face_normal = 0.25 * np.array([0,0, 1, 0,0, 1, 0,0, 1, 0,0, 1, - 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, - 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1]) - elif comm.Get_rank() == 1: - expected_face_normal = 0.25 * np.array([ 1,0,0, 1,0,0, 1,0,0, 1,0,0, - -1,0,0, -1,0,0, -1,0,0, -1,0,0, - -1,0,0, -1,0,0, -1,0,0, -1,0,0]) - - if comm.Get_rank() == 2: - expected_face_normal = 0.25 * np.array([0, 1,0, 0, 1,0, 0, 1,0, 0, 1,0, - 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, - 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0]) - - assert (face_normal == expected_face_normal).all() - -@pytest_parallel.mark.parallel(2) -def test_compute_face_normal2d(comm): - tree = maia.factory.generate_dist_block(3, 'TRI_3', comm) - maia.algo.dist.convert_elements_to_ngon(tree, comm) - zone = PT.get_all_Zone_t(tree)[0] - - face_normal = geometry.compute_face_normal(zone, comm) - - assert (face_normal == np.array([0.,0.,0.125, 0.,0.,0.125, 0.,0.,0.125, 0.,0.,0.125])).all() - -@pytest_parallel.mark.parallel(3) -def test_compute_face_center3d(comm): - tree = maia.factory.generate_dist_block(3, 'Poly', comm) - zone = PT.get_all_Zone_t(tree)[0] - - face_center = geometry.compute_face_center(zone, comm) - - if comm.Get_rank() == 0: - expected_face_center = np.array([ - 0.25, 0.25, 0. , 0.75, 0.25, 0. , 0.25, 0.75, 0. , 0.75, 0.75, 0. , - 0.25, 0.25, 0.5, 0.75, 0.25, 0.5, 0.25, 0.75, 0.5, 0.75, 0.75, 0.5, - 0.25, 0.25, 1. , 0.75, 0.25, 1. , 0.25, 0.75, 1. , 0.75, 0.75, 1. , - ]) - elif comm.Get_rank() == 1: - expected_face_center = np.array([ - 0. , 0.25, 0.25, 0. , 0.75, 0.25, 0., 0.25, 0.75, 0. , 0.75, 0.75, - 0.5, 0.25, 0.25, 0.5, 0.75, 0.25, 0.5, 0.25, 0.75, 0.5, 0.75, 0.75, - 1. , 0.25, 0.25, 1. , 0.75, 0.25, 1., 0.25, 0.75, 1. , 0.75, 0.75, - ]) - if comm.Get_rank() == 2: - expected_face_center = np.array([ - 0.25, 0. , 0.25, 0.25, 0. , 0.75, 0.75, 0. , 0.25, 0.75, 0. , 0.75, - 0.25, 0.5, 0.25, 0.25, 0.5, 0.75, 0.75, 0.5, 0.25, 0.75, 0.5, 0.75, - 0.25, 1. , 0.25, 0.25, 1. , 0.75, 0.75, 1. , 0.25, 0.75, 1. , 0.75, - ]) - assert (face_center == expected_face_center).all() - -@pytest_parallel.mark.parallel(2) -def test_compute_face_center2d(comm): - tree = maia.factory.generate_dist_block(3, 'TRI_3', comm) - maia.algo.dist.convert_elements_to_ngon(tree, comm) - zone = PT.get_all_Zone_t(tree)[0] - face_center = geometry.compute_face_center(zone, comm) - - if comm.Get_rank() == 0: - assert (face_center == np.array([1.,1,0, 2,2,0, 4,1,0, 5,2,0]) / 6.).all() - if comm.Get_rank() == 1: - assert (face_center == np.array([1.,4,0, 2,5,0, 4,4,0, 5,5,0]) / 6.).all() - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("elt_kind", ["NFACE_n", "Poly"]) -def test_compute_cell_center(elt_kind, comm): - tree = maia.factory.generate_dist_block(3, elt_kind, comm) - zone = PT.get_all_Zone_t(tree)[0] - - cell_center = geometry.compute_cell_center(zone, comm) - - if comm.Get_rank() == 0: - expt_cell_center = np.array([0.25,0.25,0.25, 0.75,0.25,0.25, 0.25,0.75,0.25, 0.75,0.75,0.25]) - if comm.Get_rank() == 1: - expt_cell_center = np.array([0.25,0.25,0.75, 0.75,0.25,0.75, 0.25,0.75,0.75, 0.75,0.75,0.75]) - - assert np.array_equal(expt_cell_center, cell_center) \ No newline at end of file diff --git a/maia/algo/dist/test/test_matching_jns_tools.py b/maia/algo/dist/test/test_matching_jns_tools.py deleted file mode 100644 index 0a53d34e..00000000 --- a/maia/algo/dist/test/test_matching_jns_tools.py +++ /dev/null @@ -1,277 +0,0 @@ -import pytest -import pytest_parallel - -import mpi4py.MPI as MPI -import numpy as np -import maia.pytree as PT - -from maia.pytree.yaml import parse_yaml_cgns -from maia.algo.dist import matching_jns_tools as MJT -from maia.factory import full_to_dist - -def test_gc_is_reference(): - pr = np.array([[1,1], [1,10], [1,10]], order='F') - prd = np.array([[20,10], [1,10], [5,5]], order='F') - gc = PT.new_GridConnectivity1to1(donor_name='Base/ZoneB', point_range=pr, point_range_donor=prd) - assert MJT.gc_is_reference(gc, 'Base/ZoneA') == True - gc = PT.new_GridConnectivity1to1(donor_name='Base/ZoneA', point_range=pr, point_range_donor=prd) - assert MJT.gc_is_reference(gc, 'Base/ZoneB') == False - gc = PT.new_GridConnectivity1to1(donor_name='Aase/ZoneA', point_range=pr, point_range_donor=prd) - assert MJT.gc_is_reference(gc, 'Base/ZoneA') == False - gc = PT.new_GridConnectivity1to1(donor_name='Base/ZoneA', point_range=pr, point_range_donor=prd) - assert MJT.gc_is_reference(gc, 'Base/ZoneA') == True - with pytest.raises(ValueError): - gc = PT.new_GridConnectivity1to1(donor_name='Base/ZoneA', point_range=pr, point_range_donor=pr) - MJT.gc_is_reference(gc, 'Base/ZoneA') - -class Test_compare_pointrange(): - def test_ok(self): - jn1 = PT.new_GridConnectivity1to1(point_range =[[17,17],[3,9],[1,5]], point_range_donor=[[7,1],[9,9],[5,1]]) - jn2 = PT.new_GridConnectivity1to1(point_range_donor=[[17,17],[3,9],[1,5]], point_range =[[7,1],[9,9],[5,1]]) - assert(MJT._compare_pointrange(jn1, jn2) == True) - jn1 = PT.new_GridConnectivity1to1(point_range =[[17,17],[3,9],[1,5]], point_range_donor=[[7,1],[9,9],[5,1]]) - jn2 = PT.new_GridConnectivity1to1(point_range_donor=[[17,17],[3,9],[1,5]], point_range =[[1,7],[9,9],[1,5]]) - assert(MJT._compare_pointrange(jn1, jn2) == True) - def test_ko(self): - jn1 = PT.new_GridConnectivity1to1(point_range =[[17,17],[3,9],[1,5]], point_range_donor=[[7,1],[9,9],[5,1]]) - jn2 = PT.new_GridConnectivity1to1(point_range_donor=[[17,17],[3,7],[1,5]], point_range =[[1,5],[9,9],[1,5]]) - assert(MJT._compare_pointrange(jn1, jn2) == False) - jn1 = PT.new_GridConnectivity1to1(point_range =[[17,17],[3,9]], point_range_donor=[[7,1],[9,9]]) - jn2 = PT.new_GridConnectivity1to1(point_range_donor=[[17,17],[3,9],[1,5]], point_range =[[7,1],[9,9],[5,1]]) - assert(MJT._compare_pointrange(jn1, jn2) == False) - def test_empty(self): # We have no idea of why this one is working, so we let it too see if its break one day - jn1 = PT.new_GridConnectivity1to1(point_range =np.empty((3,2), np.int32), point_range_donor=np.empty((3,2), np.int32)) - jn2 = PT.new_GridConnectivity1to1(point_range_donor=np.empty((3,2), np.int32), point_range =np.empty((3,2), np.int32)) - assert(MJT._compare_pointrange(jn1, jn2) == True) - -class Test_compare_pointlist(): - def test_ok(self): - jn1 = PT.new_GridConnectivity(type='Abutting1to1', point_list =[[12,14,16,18]], point_list_donor=[[9,7,5,3]]) - jn2 = PT.new_GridConnectivity(type='Abutting1to1', point_list_donor=[[12,14,16,18]], point_list =[[9,7,5,3]]) - assert(MJT._compare_pointlist(jn1, jn2) == True) - def test_ko(self): - jn1 = PT.new_GridConnectivity(type='Abutting1to1', point_list =[[12,14,16,18]], point_list_donor=[[9,7,5,3]]) - jn2 = PT.new_GridConnectivity(type='Abutting1to1', point_list_donor=[[12,14,16,18]], point_list =[[3,9,5,7]]) - assert(MJT._compare_pointlist(jn1, jn2) == False) - def test_empty(self): - jn1 = PT.new_GridConnectivity(type='Abutting1to1', point_list =np.empty((1,0), np.int32), point_list_donor=np.empty((1,0), np.int32)) - jn2 = PT.new_GridConnectivity(type='Abutting1to1', point_list_donor=np.empty((1,0), np.int32), point_list =np.empty((1,0), np.int32)) - assert(MJT._compare_pointlist(jn1, jn2) == True) - -@pytest_parallel.mark.parallel([1,3]) -def test_add_joins_donor_name(comm): - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneA Zone_t [[27,8,0]]: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - matchAB GridConnectivity1to1_t "ZoneB": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[1,4,7,10]]: # HERE - PointListDonor IndexArray_t [[13,16,7,10]]: # HERE - ZoneB Zone_t [[27,8,0]]: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - matchBA GridConnectivity_t "ZoneA": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[13,16,7,10]]: # HERE - PointListDonor IndexArray_t [[1,4,7,10]]: # HERE - GridConnectivityType GridConnectivityType_t "Abutting1to1": - matchBC1 GridConnectivity_t "Base1/ZoneC": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[32,34]]: - PointListDonor IndexArray_t [[1,3]]: - GridConnectivityType GridConnectivityType_t "Abutting1to1": - matchBC2 GridConnectivity_t "Base1/ZoneC": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[33,35]]: - PointListDonor IndexArray_t [[2,4]]: - GridConnectivityType GridConnectivityType_t "Abutting1to1": -Base1 CGNSBase_t [3,3]: - ZoneC Zone_t [[18,4,0]]: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - matchCB2 GridConnectivity1to1_t "Base0/ZoneB": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[2,4]]: # HERE - PointListDonor IndexArray_t [[33,35]]: # HERE - matchCB1 GridConnectivity1to1_t "Base0/ZoneB": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[1,3]]: - PointListDonor IndexArray_t [[32,34]]: -""" - full_tree = parse_yaml_cgns.to_cgns_tree(yt) - dist_tree = full_to_dist.full_to_dist_tree(full_tree, comm) - - MJT.add_joins_donor_name(dist_tree, comm) - - expected_donor_names = ['matchBA', 'matchAB', 'matchCB1', 'matchCB2', 'matchBC2', 'matchBC1'] - query = lambda n : PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - for i, jn in enumerate(PT.iter_nodes_from_predicate(dist_tree, query)): - assert PT.get_value(PT.get_child_from_name(jn, 'GridConnectivityDonorName')) == expected_donor_names[i] - -@pytest_parallel.mark.parallel(1) -def test_force(comm): - yt = """ -Base0 CGNSBase_t: - ZoneA Zone_t: - ZGC ZoneGridConnectivity_t: - matchAB GridConnectivity_t "ZoneB": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - PointList IndexArray_t [1,4,7,10]: - PointListDonor IndexArray_t [13,16,7,10]: - GridConnectivityDonorName Descriptor_t "WrongOldValue": - ZoneB Zone_t: - ZGC ZoneGridConnectivity_t: - matchBA GridConnectivity_t "ZoneA": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - PointList IndexArray_t [13,16,7,10]: - PointListDonor IndexArray_t [1,4,7,10]: - GridConnectivityDonorName Descriptor_t "WrongOldValue": -""" - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - jn_donor_path = 'Base0/ZoneA/ZGC/matchAB/GridConnectivityDonorName' - assert PT.get_value(PT.get_node_from_path(dist_tree, jn_donor_path)) == 'WrongOldValue' - MJT.add_joins_donor_name(dist_tree, comm) - assert PT.get_value(PT.get_node_from_path(dist_tree, jn_donor_path)) == 'WrongOldValue' - MJT.add_joins_donor_name(dist_tree, comm, force=True) - assert PT.get_value(PT.get_node_from_path(dist_tree, jn_donor_path)) == 'matchBA' - -@pytest_parallel.mark.parallel(1) -def test_some_computed(comm): - yt = """ -Base0 CGNSBase_t: - ZoneA Zone_t: - ZGC ZoneGridConnectivity_t: - matchAB.0 GridConnectivity_t "ZoneB": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - PointList IndexArray_t [1,4,7,10]: - PointListDonor IndexArray_t [13,16,7,10]: - GridConnectivityDonorName Descriptor_t "matchBA.0": - matchAB.1 GridConnectivity_t "ZoneB": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - PointList IndexArray_t [7,10]: - PointListDonor IndexArray_t [7,10]: - ZoneB Zone_t: - ZGC ZoneGridConnectivity_t: - matchBA.0 GridConnectivity_t "ZoneA": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - PointList IndexArray_t [13,16,7,10]: - PointListDonor IndexArray_t [1,4,7,10]: - GridConnectivityDonorName Descriptor_t "matchAB.0": - matchBA.1 GridConnectivity_t "ZoneA": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - PointList IndexArray_t [7,10]: - PointListDonor IndexArray_t [7,10]: -""" - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - MJT.add_joins_donor_name(dist_tree, comm) - -class Test_gcdonorname_utils: - dt = """ -Base CGNSBase_t: - ZoneA Zone_t: - ZGC ZoneGridConnectivity_t: - perio1 GridConnectivity_t 'ZoneA': - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t 'perio2': - PointList IndexArray_t [[1,3]]: - perio2 GridConnectivity_t 'Base/ZoneA': - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t 'perio1': - PointList IndexArray_t [[2,4]]: - match1 GridConnectivity_t 'ZoneB': - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t 'match2': - PointList IndexArray_t [[10,100]]: - GridLocation GridLocation_t "FaceCenter": - ZoneB Zone_t: - ZGC ZoneGridConnectivity_t: - match2 GridConnectivity_t 'ZoneA': - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t 'match1': - PointList IndexArray_t [[-100,-10]]: - GridLocation GridLocation_t "FaceCenter": - """ - dist_tree = parse_yaml_cgns.to_cgns_tree(dt) - - def test_get_jn_donor_path(self): - assert MJT.get_jn_donor_path(self.dist_tree, 'Base/ZoneA/ZGC/perio2') == 'Base/ZoneA/ZGC/perio1' - assert MJT.get_jn_donor_path(self.dist_tree, 'Base/ZoneA/ZGC/match1') == 'Base/ZoneB/ZGC/match2' - - def test_update_jn_name(self): - dist_tree = PT.deep_copy(self.dist_tree) - ini_gc = PT.get_node_from_path(dist_tree, 'Base/ZoneA/ZGC/perio2') - MJT.update_jn_name(dist_tree, 'Base/ZoneA/ZGC/perio2', 'PERIO2') - assert ini_gc[0] == 'PERIO2' - assert PT.get_value(PT.get_node_from_path(dist_tree, 'Base/ZoneA/ZGC/perio1/GridConnectivityDonorName')) == 'PERIO2' - - def test_get_matching_jns(self): - pathes = MJT.get_matching_jns(self.dist_tree) - assert pathes[0] == ('Base/ZoneA/ZGC/perio1', 'Base/ZoneA/ZGC/perio2') - assert pathes[1] == ('Base/ZoneA/ZGC/match1', 'Base/ZoneB/ZGC/match2') - pathes = MJT.get_matching_jns(self.dist_tree, lambda n: PT.Subset.GridLocation(n) == 'Vertex') - assert len(pathes) == 1 - assert pathes[0] == ('Base/ZoneA/ZGC/perio1', 'Base/ZoneA/ZGC/perio2') - - def test_match_jn_from_ordinals(self): - dist_tree = PT.deep_copy(self.dist_tree) - MJT.copy_donor_subset(dist_tree) - expected_pl_opp = [[2,4], [1,3], [-100,-10], [10,100]] - for i, jn in enumerate(PT.iter_nodes_from_label(dist_tree, 'GridConnectivity_t')): - assert (PT.get_child_from_name(jn, 'PointListDonor')[1] == expected_pl_opp[i]).all() - - def test_store_interfaces_ids(self): - dist_tree = PT.deep_copy(self.dist_tree) - MJT.store_interfaces_ids(dist_tree) - expected_id = [1,1,2,2] - expected_pos = [0,1,0,1] - for i, jn in enumerate(PT.iter_nodes_from_label(dist_tree, 'GridConnectivity_t')): - assert (PT.get_child_from_name(jn, 'DistInterfaceId')[1] == expected_id[i]).all() - assert (PT.get_child_from_name(jn, 'DistInterfaceOrd')[1] == expected_pos[i]).all() - - -def test_clear_interfaces_ids(): - yt = """ -Base0 CGNSBase_t: - ZoneA Zone_t: - ZGC ZoneGridConnectivity_t: - matchAB GridConnectivity_t "ZoneB": - DistInterfaceId DataArray_t [1]: - ZoneB Zone_t: - ZGC ZoneGridConnectivity_t: - matchBA GridConnectivity_t "ZoneA": - PointList IndexArray_t [[13,16,7,10]]: -""" - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - MJT.clear_interface_ids(dist_tree) - assert PT.get_node_from_name(dist_tree, 'DistInterfaceId') is None - assert PT.get_node_from_name(dist_tree, 'DistInterfaceOrd') is None - -@pytest_parallel.mark.parallel(1) -def test_sort_jn_pointlist(comm): - yt = """ -Base CGNSBase_t: - ZoneA Zone_t [[3, 2, 0]]: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - perio1 GridConnectivity_t 'ZoneA': - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t 'perio2': - PointList IndexArray_t [[7,5,3]]: - PointListDonor IndexArray_t [[11,12,13]]: - perio2 GridConnectivity_t 'Base/ZoneA': - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t 'perio1': - PointList IndexArray_t [[11,12,13]]: - PointListDonor IndexArray_t [[7,5,3]]: -""" - full_tree = parse_yaml_cgns.to_cgns_tree(yt) - dist_tree = full_to_dist.full_to_dist_tree(full_tree, comm) - - MJT.sort_jn_pointlist(dist_tree, comm) - - assert (PT.get_node_from_path(dist_tree, 'Base/ZoneA/ZGC/perio1/PointList')[1] == [[3,5,7]]).all() - assert (PT.get_node_from_path(dist_tree, 'Base/ZoneA/ZGC/perio2/PointListDonor')[1] == [[3,5,7]]).all() - assert (PT.get_node_from_path(dist_tree, 'Base/ZoneA/ZGC/perio2/PointList')[1] == [[13,12,11]]).all() - assert (PT.get_node_from_path(dist_tree, 'Base/ZoneA/ZGC/perio1/PointListDonor')[1] == [[13,12,11]]).all() diff --git a/maia/algo/dist/test/test_merge.py b/maia/algo/dist/test/test_merge.py deleted file mode 100644 index 93c22c94..00000000 --- a/maia/algo/dist/test/test_merge.py +++ /dev/null @@ -1,205 +0,0 @@ -import pytest -import pytest_parallel -from mpi4py import MPI -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.pytree.yaml import parse_yaml_cgns -from maia.algo.dist import matching_jns_tools as MJT -from maia.factory import full_to_dist as F2D -from maia.factory.dcube_generator import dcube_generate - -from maia.algo.dist import merge - -@pytest_parallel.mark.parallel([1,3]) -@pytest.mark.parametrize("merge_bc_from_name", [True, False]) # __ -def test_merge_zones_L(comm, merge_bc_from_name): # | | - # Setup : create 3 2*2*2 cubes and make them connected in L # __|__| - n_vtx = 3 # | | | - dcubes = [dcube_generate(n_vtx, 1., [0,0,0], comm), # |__|__| - dcube_generate(n_vtx, 1., [1,0,0], comm), # - dcube_generate(n_vtx, 1., [1,1,0], comm)] - zones = [PT.get_all_Zone_t(dcube)[0] for dcube in dcubes] - tree = PT.new_CGNSTree() - base = PT.new_CGNSBase(parent=tree) - # After PDM, each boundary has a different distribution which makes difficult to - # convert it to joins. Re distribution is done below - for izone, zone in enumerate(zones): - zone[0] = f'zone{izone+1}' - PT.add_child(base, zone) - for bc in PT.iter_nodes_from_label(zone, 'BC_t'): - pl = PT.get_child_from_name(bc, 'PointList') - distri = MT.getDistribution(bc, 'Index') - data = {'PointList' : pl[1][0]} - distri_new, data_new = merge._equilibrate_data(data, comm, distri=distri[1]) - PT.set_value(distri, distri_new) - PT.set_value(pl, data_new['PointList'].reshape((1,-1), order='F')) - - #Setup connections - jn_cur = [['Xmax'], ['Xmin', 'Ymax'], ['Ymin']] #To copy to create jn - jn_opp = [['Xmin'], ['Xmax', 'Ymin'], ['Ymax']] #To copy to create pld - zone_opp = [['zone2'], ['zone1', 'zone3'], ['zone2']] - for izone, zone in enumerate(zones): - for j,bc_n in enumerate(jn_cur[izone]): - bc = PT.get_node_from_name(zone, bc_n) - PT.rm_nodes_from_name(zone, bc_n) - zgc = PT.update_child(zone, 'ZoneGridConnectivity', 'ZoneGridConnectivity_t') - gc = PT.new_GridConnectivity(f'match{j}', f'{zone_opp[izone][j]}', 'Abutting1to1', parent=zgc) - for name in [':CGNS#Distribution', 'GridLocation', 'PointList']: - PT.add_child(gc, PT.get_child_from_name(bc, name)) - ref_bc = PT.get_node_from_name(tree, f'{jn_opp[izone][j]}') - PT.new_IndexArray('PointListDonor', PT.get_child_from_name(ref_bc, 'PointList')[1].copy(), parent=gc) - - #Setup some data - for i_zone, zone in enumerate(zones): - sol = PT.new_FlowSolution('FlowSolution', loc='Vertex', parent=zone) - PT.new_DataArray('DomId', (i_zone+1)*np.ones(n_vtx**3, int), parent=sol) - pl_sol_full = PT.new_FlowSolution('PartialSol', loc='CellCenter') - PT.new_IndexArray('PointList', value=np.array([[8]], pdm_dtype), parent=pl_sol_full) - PT.new_DataArray('SpecificSol', np.array([3.14]), parent=pl_sol_full) - PT.add_child(zone, F2D.distribute_pl_node(pl_sol_full, comm)) - - # If we use private func, we need to add ordinals - MJT.add_joins_donor_name(tree, comm) - subset_merge = "name" if merge_bc_from_name else "none" - merged_zone = merge._merge_zones(tree, comm, subset_merge_strategy=subset_merge) - - assert PT.Zone.n_cell(merged_zone) == 3*((n_vtx-1)**3) - assert PT.Zone.n_vtx(merged_zone) == 3*(n_vtx**3) - 2*(n_vtx**2) - assert PT.Zone.n_face(merged_zone) == 3*(3*n_vtx*(n_vtx-1)**2) - 2*(n_vtx-1)**2 - assert PT.get_node_from_label(merged_zone, 'ZoneGridConnectivity_t') is None - if merge_bc_from_name: - assert len(PT.get_nodes_from_label(merged_zone, 'BC_t')) == 6 #BC merged by name - else: - assert len(PT.get_nodes_from_label(merged_zone, 'BC_t')) == 3*6 - 4 #BC not merged - assert comm.allreduce(PT.get_node_from_name(merged_zone, 'DomId')[1].size, MPI.SUM) == PT.Zone.n_vtx(merged_zone) - - expected_partial_sol_size = 3 if merge_bc_from_name else 1 - assert comm.allreduce(PT.get_node_from_name(merged_zone, 'SpecificSol')[1].size, MPI.SUM) == expected_partial_sol_size - if merge_bc_from_name: - partial_pl = PT.get_node_from_path(merged_zone, 'PartialSol/PointList') - assert (np.concatenate(comm.allgather(partial_pl[1][0])) == [8,16,24]).all() - -@pytest.mark.parametrize("merge_only_two", [False, True]) -@pytest_parallel.mark.parallel(1) -def test_merge_zones_I(comm, merge_only_two): - """ A setup with 3 zones in I direction connected by match - jns (2) + 1 periodic between first and last zone. - We request to merge only the two first zones - """ - # Setup : create 3 2*2*2 cubes and make them connected in I - n_vtx = 3 - dcubes = [dcube_generate(n_vtx, 1., [0,0,0], comm), - dcube_generate(n_vtx, 1., [1,0,0], comm), - dcube_generate(n_vtx, 1., [2,0,0], comm)] - zones = [PT.get_all_Zone_t(dcube)[0] for dcube in dcubes] - tree = PT.new_CGNSTree() - base = PT.new_CGNSBase(parent=tree) - # After PDM, each boundary has a different distribution which makes difficult to - # convert it to joins. Re distribution is done below - for izone, zone in enumerate(zones): - zone[0] = f'zone{izone+1}' - PT.add_child(base, zone) - for bc in PT.iter_nodes_from_label(zone, 'BC_t'): - pl = PT.get_child_from_name(bc, 'PointList') - distri = MT.getDistribution(bc, 'Index') - data = {'PointList' : pl[1][0]} - distri_new, data_new = merge._equilibrate_data(data, comm, distri=distri[1]) - PT.set_value(distri, distri_new) - PT.set_value(pl, data_new['PointList'].reshape((1,-1), order='F')) - - #Setup connections - jn_cur = [['Xmax'], ['Xmin', 'Xmax'], ['Xmin']] #To copy to create jn - jn_opp = [['Xmin'], ['Xmax', 'Xmin'], ['Xmax']] #To copy to create pld - zone_opp = [['zone2'], ['zone1', 'zone3'], ['zone2']] - for izone, zone in enumerate(zones): - for j,bc_n in enumerate(jn_cur[izone]): - bc = PT.get_node_from_name(zone, bc_n) - PT.rm_nodes_from_name(zone, bc_n) - zgc = PT.update_child(zone, 'ZoneGridConnectivity', 'ZoneGridConnectivity_t') - gc = PT.new_GridConnectivity(f'match{j}', f'{zone_opp[izone][j]}', 'Abutting1to1', parent=zgc) - for name in [':CGNS#Distribution', 'GridLocation', 'PointList']: - PT.add_child(gc, PT.get_child_from_name(bc, name)) - ref_bc = PT.get_node_from_name(tree, f'{jn_opp[izone][j]}') - PT.new_IndexArray('PointListDonor', PT.get_child_from_name(ref_bc, 'PointList')[1].copy(), parent=gc) - # Add periodic between first and last - jn_cur = ['Xmin', 'Xmax'] #To copy to create jn - jn_opp = ['Xmax', 'Xmin'] #To copy to create pld - zone_opp = ['zone3', 'zone1'] - for izone, zone in zip(range(2), [zones[0], zones[-1]]): - bc = PT.get_node_from_name(zone, jn_cur[izone]) - zgc = PT.get_node_from_label(zone, 'ZoneGridConnectivity_t') - gc = PT.new_GridConnectivity('perio', f'{zone_opp[izone]}', 'Abutting1to1', parent=zgc) - for name in [':CGNS#Distribution', 'GridLocation', 'PointList']: - PT.add_child(gc, PT.get_child_from_name(bc, name)) - ref_bc = PT.get_node_from_name(tree, f'{jn_opp[izone]}') - PT.new_IndexArray('PointListDonor', PT.get_child_from_name(ref_bc, 'PointList')[1].copy(), parent=gc) - sign = 1 if izone == 0 else -1 - PT.new_GridConnectivityProperty(periodic={'translation' : [sign*3., 0, 0]}, parent=gc) - for izone, zone in zip(range(2), [zones[0], zones[-1]]): - PT.rm_nodes_from_name(zone, jn_cur[izone]) - - #Setup some data (Only one rank so next lines are OK) - zsr_full = PT.new_ZoneSubRegion('SubRegion', bc_name='Ymin', loc='FaceCenter', parent=zones[1]) - old_id = PT.get_node_from_path(zones[1], 'ZoneBC/Ymin/PointList')[1][0].copy() - PT.new_DataArray('OldId', old_id, parent=zsr_full) - - - if merge_only_two: - n_merged = 2 - merge.merge_zones(tree, ['Base/zone1', 'Base/zone2'], comm, output_path='MergedBase/MergedZone') - assert len(PT.get_all_CGNSBase_t(tree)) == len(PT.get_all_CGNSBase_t(tree)) == 2 - merged_zone = PT.get_node_from_path(tree, 'MergedBase/MergedZone') - assert len(PT.get_nodes_from_label(merged_zone, 'GridConnectivity_t')) == 2 - assert len(PT.get_nodes_from_label(merged_zone, 'Periodic_t')) == 1 - else: - n_merged = 3 - merge.merge_connected_zones(tree, comm) - assert len(PT.get_all_Zone_t(tree)) == 1 - merged_zone = PT.get_all_Zone_t(tree)[0] - - assert len(PT.get_nodes_from_label(merged_zone, 'GridConnectivity_t')) == 2 - for gc in PT.iter_nodes_from_label(merged_zone, 'GridConnectivity_t'): - assert PT.get_value(gc) not in ['zone1', 'zone2', 'zone3'] - assert PT.get_node_from_label(gc, 'Periodic_t') is not None - - assert len(PT.get_nodes_from_label(merged_zone, 'BC_t')) == 4 - assert PT.Zone.n_cell(merged_zone) == n_merged*((n_vtx-1)**3) - assert PT.Zone.n_vtx(merged_zone) == n_merged*(n_vtx**3) - (n_merged-1)*(n_vtx**2) - assert PT.Zone.n_face(merged_zone) == n_merged*(3*n_vtx*(n_vtx-1)**2) - (n_merged-1)*(n_vtx-1)**2 - assert PT.get_node_from_path(merged_zone, 'SubRegion/GridLocation') is not None - assert PT.get_node_from_path(merged_zone, 'SubRegion/BCRegionName') is None - assert (PT.get_node_from_path(merged_zone, 'SubRegion/OldId')[1] == old_id).all() - assert not (PT.get_node_from_path(merged_zone, 'SubRegion/PointList')[1] == old_id).all() - -@pytest_parallel.mark.parallel(3) -def test_equilibrate_data(comm): - - rank = comm.Get_rank() - data = {'rank' : rank * np.ones(10*rank, np.int32), - 'range': np.arange(10*rank).astype(float)} #unequilibrated data - - current_distri_f = np.array([0, 0, 10, 30], pdm_dtype) - current_distri = current_distri_f[[rank, rank+1, comm.Get_size()]] - - expected_distri = np.array([0, 10, 20, 30])[[rank, rank+1, comm.Get_size()]] - expected_rank_f = np.concatenate([np.ones(10, np.int32), 2*np.ones(20, np.int32)]) - expected_range_f = np.concatenate([np.arange(10), np.arange(20)]).astype(float) - - distri, data_eq = merge._equilibrate_data(data, comm) - assert (distri == expected_distri).all() - assert (data_eq['rank'] == expected_rank_f[distri[0]:distri[1]]).all() - assert (data_eq['range'] == expected_range_f[distri[0]:distri[1]]).all() - - distri, data_eq = merge._equilibrate_data(data, comm, distri=current_distri) - assert (distri == expected_distri).all() - assert (data_eq['rank'] == expected_rank_f[distri[0]:distri[1]]).all() - assert (data_eq['range'] == expected_range_f[distri[0]:distri[1]]).all() - - distri, data_eq = merge._equilibrate_data(data, comm, distri_full=current_distri_f) - assert (distri == expected_distri).all() - assert (data_eq['rank'] == expected_rank_f[distri[0]:distri[1]]).all() - assert (data_eq['range'] == expected_range_f[distri[0]:distri[1]]).all() diff --git a/maia/algo/dist/test/test_merge_ids.py b/maia/algo/dist/test/test_merge_ids.py deleted file mode 100644 index 61a021cb..00000000 --- a/maia/algo/dist/test/test_merge_ids.py +++ /dev/null @@ -1,56 +0,0 @@ -import pytest -import pytest_parallel - -import numpy as np - -from maia.algo.dist import merge_ids - -@pytest_parallel.mark.parallel(2) -def test_remove_distributed_ids(comm): - if comm.Get_rank() == 0: - distri = np.array([0,7,13], np.int32) - ids = np.array([1, 6,12,9], np.int32) - expected_old_to_new = [-1,1,2,3,4,-1,5] - elif comm.Get_rank() == 1: - distri = np.array([7,13,13], dtype=np.int32) - ids = np.empty(0, dtype=np.int32) - expected_old_to_new = [6,-1,7,8,-1,9] - - old_to_new = merge_ids.remove_distributed_ids(distri, ids, comm) - assert (old_to_new == expected_old_to_new).all() - -@pytest_parallel.mark.parallel(2) -def test_merge_distributed_ids(comm): - if comm.Get_rank() == 0: - distri = np.array([0,7,13], np.int32) - ids = np.array([6,12,9], np.int32) - targets = np.array([1,10,7], np.int32) - expected_old_to_new = [1,2,3,4,5,1,6] - elif comm.Get_rank() == 1: - distri = np.array([7,13,13], dtype=np.int32) - ids = np.empty(0, dtype=np.int32) - targets = np.empty(0, dtype=np.int32) - expected_old_to_new = [7,6,8,9,8,10] - - old_to_new = merge_ids.merge_distributed_ids(distri, ids, targets, comm) - assert (old_to_new == expected_old_to_new).all() - - if comm.Get_rank() == 0: - distri = np.array([0,7,13]) - ids = np.array([6,9]) - targets = np.array([1,7]) - expected_old_to_new = [1,2,3,4,5,1,6] - expected_signed_old_to_new = [1,2,3,4,5,-1,6] - elif comm.Get_rank() == 1: - distri = np.array([7,13,13]) - ids = np.array([12]) - targets = np.array([10]) - expected_old_to_new = [7,6,8,9,8,10] - expected_signed_old_to_new = [7,-6,8,9,-8,10] - - old_to_new = merge_ids.merge_distributed_ids(distri, ids, targets, comm) - assert (old_to_new == expected_old_to_new).all() - - signed_old_to_new = merge_ids.merge_distributed_ids(distri, ids, targets, comm, True) - assert (signed_old_to_new == expected_signed_old_to_new).all() - diff --git a/maia/algo/dist/test/test_merge_jn.py b/maia/algo/dist/test/test_merge_jn.py deleted file mode 100644 index 58c81ae5..00000000 --- a/maia/algo/dist/test/test_merge_jn.py +++ /dev/null @@ -1,246 +0,0 @@ -import pytest_parallel -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.factory import dcube_generator -from maia.pytree.yaml import parse_yaml_cgns -from maia.utils import par_utils - -from maia.algo.dist import merge_jn as MJ - -@pytest_parallel.mark.parallel(2) -def test_update_ngon(comm): - tree = dcube_generator.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - PT.rm_nodes_from_label(tree, 'ZoneBC_t') - - ngon = PT.get_node_from_name(zone, 'NGonElements') - vtx_distri_ini = PT.get_node_from_path(zone, ':CGNS#Distribution/Vertex')[1] - - #from maia.transform.dist_tree.merge_ids import merge_distributed_ids - #old_to_new_vtx = merge_distributed_ids(vtx_distri_ini, np.array([12,21,15,24,18,27]), \ - # np.array([10,19,13,22,16,25]), comm) #Used to get old_to_new_vtx - old_to_new_vtx_full = np.array([1,2,3,4,5,6,7,8,9,10,9,11,12,11,13,14,13,15,16,15,17,18,17,19,20,19,21]) - - old_to_new_vtx = old_to_new_vtx_full[vtx_distri_ini[0]:vtx_distri_ini[1]] - if comm.Get_rank() == 1: - ref_faces = np.array([15,23], dtype=pdm_dtype) - del_faces = np.array([16,24], dtype=pdm_dtype) - else: - ref_faces = np.array([], dtype=pdm_dtype) - del_faces = np.array([], dtype=pdm_dtype) - - expected_pe_full = np.array([[35,0],[36,0],[37,0],[38,0],[35,39],[36,40],[37,41],[38,42],[39,0],[40,0],[41,0],[42,0], - [35,0],[37,0],[39,41],[35,36],[37,38],[39,40],[41,42],[36,0],[38,0],[40,42],[35,0],[39,0], - [36,0],[40,0],[35,37],[39,41],[36,38],[40,42],[37,0],[41,0],[38,0],[42,0]]) - expected_ec_full = np.array([ 2, 5, 4, 1, 3, 6, 5, 2, 5, 8, 7, 4, 6, 9, 8, 5, 10,12,11, 9, 9,11,13,11, - 12,14,13,11, 11,13,15,13, 16,18,17,15, 15,17,19,17, 18,20,19,17, 17,19,21,19, - 1, 4,12,10, 4, 7,14,12, 10,12,18,16, 9,11, 5, 2, 11,13, 8, 5, 15,17,11, 9, - 17,19,13,11, 11,13, 6, 3, 13,15, 9, 6, 17,19,13,11, 10, 9, 2, 1, 16,15, 9,10, - 9,11, 3, 2, 15,17,11, 9, 4, 5,11,12, 12,11,17,18, 5, 6,13,11, 11,13,19,17, - 7, 8,13,14, 14,13,19,20, 8, 9,15,13, 13,15,21,19]) - expected_eso_f = np.arange(0, 4*34+1, 4) - - MJ._update_ngon(ngon, ref_faces, del_faces, vtx_distri_ini, old_to_new_vtx, comm) - - start, end = PT.get_node_from_path(ngon, ':CGNS#Distribution/Element')[1][[0,1]] - start_e, end_e = PT.get_node_from_path(ngon, ':CGNS#Distribution/ElementConnectivity')[1][[0,1]] - assert (PT.get_node_from_name(ngon, 'ElementRange')[1] == [1,34] ).all() - assert (PT.get_node_from_name(ngon, 'ParentElements')[1] == expected_pe_full[start:end] ).all() - assert (PT.get_node_from_name(ngon, 'ElementConnectivity')[1] == expected_ec_full[start_e:end_e]).all() - assert (PT.get_node_from_name(ngon, 'ElementStartOffset')[1] == expected_eso_f[start:end+1] ).all() - -@pytest_parallel.mark.parallel(2) -def test_update_nface(comm): - #Create nface node, not generated by dcube gen (cube 2*2*2 cells) - nface_ec_full = [1, 5, 13, 17, 25, 29, 2, 6, -17, 21, 27, 31, 3, 7, 14, 18, -29, 33, 4, 8, -18, 22, -31, 35, \ - -5, 9, 15, 19, 26, 30, -6, 10, -19, 23, 28, 32, -7, 11, 16, 20, -30, 34, -8, 12, -20, 24, -32, 36] - eso_full = np.arange(0, 6*8+1, 6) - - face_distri_ini = par_utils.uniform_distribution(36, comm) - cell_distri_ini = par_utils.uniform_distribution((3-1)**3, comm) - cell_distri_ini_e = par_utils.uniform_distribution(6*(3-1)**3, comm) - nface = PT.new_NFaceElements(erange = [36+1,36+8], - eso = eso_full[cell_distri_ini[0]:cell_distri_ini[1]+1], - ec = nface_ec_full[cell_distri_ini_e[0]:cell_distri_ini_e[1]]) - MT.newDistribution({'Element' : cell_distri_ini, 'ElementConnectivity' : cell_distri_ini_e}, nface) - - #from maia.transform.dist_tree.merge_ids import merge_distributed_ids - #old_to_new_face = merge_distributed_ids(face_distri_ini, np.array([23,24]), np.array([15,16]), comm, True) - old_to_new_face_f = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,-15,-16,23,24,25,26,27,28,29,30,31,32,33,34] - old_to_new_face = np.array(old_to_new_face_f[face_distri_ini[0]:face_distri_ini[1]]) - - MJ._update_nface(nface, face_distri_ini, old_to_new_face, n_rmvd_face=2, comm=comm) - - expected_eso_f = np.arange(0, 4*34+1, 4) - - expected_ec_full = [1, 5, 13, 17, 23, 27, 2, 6, -17, 21, 25, 29, 3, 7, 14, 18, -27, 31, 4, 8, -18, 22, -29, 33, \ - -5, 9, 15, 19, 24, 28, -6, 10, -19, -1*15, 26, 30, -7, 11, 16, 20, -28, 32, -8, 12, -20, -1*16, -30, 34] - - start_e, end_e = PT.get_node_from_path(nface, ':CGNS#Distribution/ElementConnectivity')[1][[0,1]] - assert (PT.get_node_from_name(nface, 'ElementConnectivity')[1] == expected_ec_full[start_e:end_e]).all() - assert (PT.get_node_from_name(nface, 'ElementStartOffset')[1] == eso_full[cell_distri_ini[0]:cell_distri_ini[1]+1]).all() - -@pytest_parallel.mark.parallel(2) -def test_update_subset(comm): - bc = PT.new_BC('BC') - bc_distri = par_utils.uniform_distribution(5, comm) - this_rank = slice(bc_distri[0], bc_distri[1]) - bcds = PT.new_child(bc, 'BCDataSet', 'BCDataSet_t') - bcdata = PT.new_child(bcds, 'BCData', 'BCData_t') - - data = np.array([10,20,30,20,50][this_rank], dtype=np.float64) - PT.new_DataArray('ArrayA', data, parent=bcdata) - PT.new_DataArray('ArrayB', 2*data, parent=bcdata) - - pl_new = np.array([1,2,3,2,4][this_rank], pdm_dtype) - MJ._update_subset(bc, pl_new, ['BCDataSet_t', 'BCData_t', 'DataArray_t'], comm) - - new_distri = par_utils.uniform_distribution(4, comm) - this_rank = slice(new_distri[0], new_distri[1]) - assert (PT.get_node_from_path(bc, ':CGNS#Distribution/Index')[1] == new_distri).all() - assert (PT.get_node_from_name(bc, 'PointList')[1] == [1,2,3,4][this_rank]).all() - assert (PT.get_node_from_name(bc, 'ArrayA')[1] == [10,20,30,50][this_rank]).all() - assert (PT.get_node_from_name(bc, 'ArrayB')[1] == [20,40,60,100][this_rank]).all() - -@pytest_parallel.mark.parallel(2) -def test_update_cgns_subsets(comm): - tree = dcube_generator.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - #Move some node to diversify the test - zgc = PT.new_ZoneGridConnectivity('ZoneGC', parent=zone) - for bcname in ['Ymin', 'Ymax']: - bc = PT.get_node_from_name(zone, bcname) - PT.set_label(bc, 'GridConnectivity_t') - PT.add_child(zgc, bc) - PT.rm_child(PT.get_child_from_label(zone, 'ZoneBC_t'), bc) - bc = PT.get_node_from_name(zone, 'Zmin') - PT.set_label(bc, 'ZoneSubRegion_t') - PT.add_child(zone, bc) - PT.rm_child(PT.get_child_from_label(zone, 'ZoneBC_t'), bc) - PT.new_DataArray('SubSol', np.copy(PT.get_node_from_name(bc, 'PointList')[1][0]), parent=bc) - bc = PT.get_node_from_name(zone, 'Zmax') - PT.set_label(bc, 'FlowSolution_t') - PT.add_child(zone, bc) - PT.rm_child(PT.get_child_from_label(zone, 'ZoneBC_t'), bc) - PT.new_DataArray('Sol', np.copy(PT.get_node_from_name(bc, 'PointList')[1][0]), parent=bc) - - face_distri_ini = PT.get_value(MT.getDistribution(PT.get_node_from_path(zone, 'NGonElements'), 'Element')) - old_to_new_face_f = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,15,16,23,24,25,26,27,28,29,30,31,32,33,34] - old_to_new_face = np.array(old_to_new_face_f[face_distri_ini[0]:face_distri_ini[1]]) - MJ._update_cgns_subsets(zone, 'FaceCenter', face_distri_ini, old_to_new_face, 'Base', comm) - - bc_distri = par_utils.uniform_distribution(4, comm) - this_rank = slice(bc_distri[0], bc_distri[1]) - assert (PT.get_node_from_path(zone, 'Zmin/PointList')[1] == [[1,2,3,4][this_rank]]).all() - assert (PT.get_node_from_path(zone, 'Zmax/PointList')[1] == [[9,10,11,12][this_rank]]).all() - assert (PT.get_node_from_path(zone, 'ZoneBC/Xmin/PointList')[1] == [[13,14,15,16][this_rank]]).all() - assert (PT.get_node_from_path(zone, 'ZoneBC/Xmax/PointList')[1] == [[15,16,21,22][this_rank]]).all() - assert (PT.get_node_from_path(zone, 'ZoneGC/Ymin/PointList')[1] == [[23,24,25,26][this_rank]]).all() - assert (PT.get_node_from_path(zone, 'ZoneGC/Ymax/PointList')[1] == [[31,32,33,34][this_rank]]).all() - assert (PT.get_node_from_path(zone, 'Zmin/PointList')[1] == PT.get_node_from_path(zone, 'Zmin/SubSol')[1]).all() - assert (PT.get_node_from_path(zone, 'Zmax/PointList')[1] == PT.get_node_from_path(zone, 'Zmax/Sol')[1]).all() - -def test_shift_cgns_subsets(): - yt = """ - Zone Zone_t: - ZBC ZoneBC_t: - bc1 BC_t: - PointList IndexArray_t [[10,14,12,16]]: - GridLocation GridLocation_t "CellCenter": - bc2 BC_t: - PointList IndexArray_t [[1,3,5,7]]: - GridLocation GridLocation_t "Vertex": - bc3 BC_t: - PointList IndexArray_t [[1,3,5,7]]: - GridLocation GridLocation_t "Vertex": - BCDS BCDataSet_t: - PointList IndexArray_t [[50,100]]: - GridLocation GridLocation_t "CellCenter": - ZSR ZoneSubRegion_t: - GridLocation GridLocation_t "CellCenter": - PointList IndexArray_t [[100]]: - """ - zone = parse_yaml_cgns.to_node(yt) - - MJ._shift_cgns_subsets(zone, 'CellCenter', -4) - - assert (PT.get_node_from_path(zone, 'ZBC/bc1/PointList')[1] == [[6,10,8,12]]).all() - assert (PT.get_node_from_path(zone, 'ZBC/bc2/PointList')[1] == [[1,3,5,7]] ).all() - assert (PT.get_node_from_path(zone, 'ZBC/bc3/PointList')[1] == [[1,3,5,7]] ).all() - assert (PT.get_node_from_path(zone, 'ZBC/bc3/BCDS/PointList')[1] == [[46,96]] ).all() - assert (PT.get_node_from_path(zone, 'ZSR/PointList')[1] == [[96]] ).all() - -@pytest_parallel.mark.parallel(2) -def test_update_vtx_data(comm): - tree = dcube_generator.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - PT.rm_nodes_from_label(tree, 'ZoneBC_t') - distri = PT.get_value(MT.getDistribution(zone, 'Vertex')) - fs = PT.new_FlowSolution('FSol', loc='Vertex', parent=zone) - sol = PT.new_DataArray('Sol', np.arange(27)[distri[0]:distri[1]]+1, parent=fs) - - if comm.Get_rank() == 0: - vtx_to_remove = np.array([12,21,15,24]) - expected_distri = np.array([0,13,21]) - expected_cx = np.array([0.,0.5,1.,0.,0.5,1.,0.,0.5,1.,0.,0.5,0.,0.5]) - expected_sol = np.array([1,2,3,4,5,6,7,8,9,10,11,13,14]) - elif comm.Get_rank() == 1: - vtx_to_remove = np.array([18,27]) - expected_distri = np.array([13,21,21]) - expected_cx = np.array([0.,0.5,0.,0.5,0.,0.5,0.,0.5]) - expected_sol = np.array([16,17,19,20,22,23,25,26]) - - MJ._update_vtx_data(zone, vtx_to_remove, comm) - - assert (PT.get_value(MT.getDistribution(zone, 'Vertex')) == expected_distri).all() - assert (PT.get_node_from_name(zone, 'CoordinateX')[1] == expected_cx).all() - assert (PT.get_node_from_name(zone, 'Sol')[1] == expected_sol).all() - -@pytest_parallel.mark.parallel(2) -def test_merge_intrazone_jn(comm): - tree = dcube_generator.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - PT.rm_children_from_label(zone, 'ZoneBC_t') - #Create jns - zgc = PT.new_ZoneGridConnectivity('ZoneGC', parent=zone) - pl = np.array([[15,16]], pdm_dtype) if comm.Get_rank() == 0 else np.array([[]], pdm_dtype) - pld = np.array([[23,24]], pdm_dtype) if comm.Get_rank() == 0 else np.array([[]], pdm_dtype) - distri = np.array([0,2,2], pdm_dtype) if comm.Get_rank() == 0 else np.array([2,2,2], pdm_dtype) - jn = PT.new_GridConnectivity('matchA', 'zone', type='Abutting1to1', loc='FaceCenter', parent=zgc) - PT.new_IndexArray('PointList', pl, parent=jn) - PT.new_IndexArray('PointListDonor', pld, parent=jn) - MT.newDistribution({'Index' : distri}, jn) - jn = PT.new_GridConnectivity('matchB', 'zone', 'Abutting1to1', loc='FaceCenter', parent=zgc) - PT.new_IndexArray('PointList', pld, parent=jn) - PT.new_IndexArray('PointListDonor', pl, parent=jn) - MT.newDistribution({'Index' : distri}, jn) - #Other jns to ensure they are not merge - pl = np.array([[13,14]], pdm_dtype) if comm.Get_rank() == 1 else np.array([[]], pdm_dtype) - pld = np.array([[21,22]], pdm_dtype) if comm.Get_rank() == 1 else np.array([[]], pdm_dtype) - distri = np.array([0,2,2], pdm_dtype) if comm.Get_rank() == 1 else np.array([2,2,2], pdm_dtype) - jn = PT.new_GridConnectivity('matchC', 'zone', 'Abutting1to1', loc='FaceCenter', parent=zgc) - PT.new_IndexArray('PointList', pl, parent=jn) - PT.new_IndexArray('PointListDonor', pld, parent=jn) - MT.newDistribution({'Index' : distri}, jn) - jn = PT.new_GridConnectivity('matchD', 'zone', 'Abutting1to1', loc='FaceCenter', parent=zgc) - PT.new_IndexArray('PointList', pld, parent=jn) - PT.new_IndexArray('PointListDonor', pl, parent=jn) - MT.newDistribution({'Index' : distri}, jn) - - jn_pathes = ('Base/zone/ZoneGC/matchC', 'Base/zone/ZoneGC/matchD') - MJ.merge_intrazone_jn(tree, jn_pathes, comm) - - assert PT.get_node_from_name(zone, 'matchA') is not None - assert PT.get_node_from_name(zone, 'matchB') is not None - assert PT.get_node_from_name(zone, 'matchC') is None - assert PT.get_node_from_name(zone, 'matchD') is None - - assert (PT.get_node_from_path(zone, 'NGonElements/ElementRange')[1] == [1,34]).all() - assert (PT.get_node_from_path(zone, 'ZoneGC/matchA/PointList')[1] == - PT.get_node_from_path(zone, 'ZoneGC/matchB/PointListDonor')[1]).all() - assert (PT.get_node_from_path(zone, 'ZoneGC/matchB/PointList')[1] == - PT.get_node_from_path(zone, 'ZoneGC/matchA/PointListDonor')[1]).all() diff --git a/maia/algo/dist/test/test_mesh_adaptation.py b/maia/algo/dist/test/test_mesh_adaptation.py deleted file mode 100644 index 3f815305..00000000 --- a/maia/algo/dist/test/test_mesh_adaptation.py +++ /dev/null @@ -1,136 +0,0 @@ -import pytest -import pytest_parallel -import shutil - -import maia -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns - -from maia.algo.dist import mesh_adaptation as MA - -import numpy as np - -feflo_exists = shutil.which('feflo.a') is not None - -def test_unpack_metric(): - yz = """ - Base CGNSBase_t [3, 3]: - Zone Zone_t [[3,1,0]]: - ZoneType ZoneType_t "Unstructured": - FlowSol FlowSolution_t: - Mach DataArray_t R8 [1., 1., 1.]: - TensorXX DataArray_t R8 [1., 1., 1.]: - TensorZZ DataArray_t R8 [1., 1., 1.]: - TensorXY DataArray_t R8 [1., 1., 1.]: - TensorYY DataArray_t R8 [1., 1., 1.]: - TensorXZ DataArray_t R8 [1., 1., 1.]: - TensorYZ DataArray_t R8 [1., 1., 1.]: - WrongA DataArray_t R8 [1., 1., 1.]: - WrongB DataArray_t R8 [1., 1., 1.]: - WrongC DataArray_t R8 [1., 1., 1.]: - """ - tree = parse_yaml_cgns.to_cgns_tree(yz) - - # > Wrong because leads to unexistant field - with pytest.raises(ValueError): - metrics_names = PT.get_names(MA.unpack_metric(tree, "FlowSol/toto")) - - # > Wrong because leads to 3 fields - with pytest.raises(ValueError): - metrics_names = PT.get_names(MA.unpack_metric(tree, "FlowSol/Wrong")) - - # > Path to unique field - metrics_names = PT.get_names(MA.unpack_metric(tree, "FlowSol/Mach")) - assert metrics_names==["Mach"] - - # > Isotrop metric - assert MA.unpack_metric(tree, None) == [] - - # > Path to multiple fields - metrics_names = PT.get_names(MA.unpack_metric(tree, "FlowSol/Tensor")) - assert metrics_names==[ "TensorXX","TensorXY","TensorXZ", - "TensorYY","TensorYZ","TensorZZ" ] - - # > Paths to multiple fields (order matters) - metric = ["FlowSol/TensorXX", "FlowSol/TensorZZ", - "FlowSol/TensorXZ", "FlowSol/TensorXY", - "FlowSol/TensorYZ", "FlowSol/TensorYY"] - metrics_names = PT.get_names(MA.unpack_metric(tree, metric)) - assert metrics_names==[ "TensorXX","TensorZZ","TensorXZ", - "TensorXY","TensorYZ","TensorYY" ] - -@pytest.mark.skipif(not feflo_exists, reason="Require Feflo.a") -@pytest_parallel.mark.parallel(2) -def test_adapt_with_feflo(comm): - - dist_tree = maia.factory.generate_dist_block(5, 'TETRA_4', comm) - base = PT.get_node_from_label(dist_tree, 'CGNSBase_t') - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - PT.set_name(zone, 'MyZone') - bc = PT.get_node_from_name(zone, 'Xmin') - PT.set_value(bc, 'FamilySpecified') - PT.new_child(bc, 'FamilyName', 'FamilyName_t', 'SomeFamily') - - PT.new_child(base, 'SomeFamily', 'Family_t') - - # > Create a metric field - cx, cy, cz = PT.Zone.coordinates(zone) - fields= {'metric' : (cx-0.5)**5+(cy-0.5)**5 - 1} - PT.new_FlowSolution("FlowSolution", loc="Vertex", fields=fields, parent=zone) - - # > Adapt mesh according to scalar metric - adpt_dist_tree = MA.adapt_mesh_with_feflo(dist_tree, - "FlowSolution/metric", - comm, - container_names=["FlowSolution"], - feflo_opts="-c 100 -cmax 100 -p 4") - - # Parsing of meshb is already tested elsewhere, here we check that feflo did not failed - # and that metadata (eg. names, families) are well recovered - adpt_zone = PT.get_all_Zone_t(adpt_dist_tree)[0] - assert PT.get_name(adpt_zone) == 'MyZone' - assert PT.Zone.n_vtx(adpt_zone) != PT.Zone.n_vtx(zone) - adpt_bc = PT.get_node_from_name(adpt_zone, 'Xmin') - assert PT.get_value(adpt_bc) == 'FamilySpecified' - assert PT.get_value(PT.get_child_from_name(adpt_bc, 'FamilyName')) == 'SomeFamily' - assert PT.get_node_from_name_and_label(adpt_dist_tree, 'SomeFamily', 'Family_t') is not None - -@pytest.mark.skipif(not feflo_exists, reason="Require Feflo.a") -@pytest_parallel.mark.parallel(2) -def test_periodic_adapt_with_feflo(comm): - - # > Create simple mesh - dist_tree = maia.factory.generate_dist_block(3, 'TETRA_4', comm) - PT.rm_nodes_from_name(dist_tree, 'NODE*') - - # > Define metric - dist_zone = PT.get_node_from_label(dist_tree, 'Zone_t') - vtx_distri = PT.maia.getDistribution(dist_zone, 'Vertex')[1] - dn_vtx = vtx_distri[1] - vtx_distri[0] - fld_metric = np.ones(dn_vtx, dtype=float) - PT.new_FlowSolution('Metric', loc='Vertex', fields={'metric':fld_metric}, parent=dist_zone) - - # > Build periodicities - zone_bc_n = PT.get_node_from_label(dist_tree, 'ZoneBC_t') - for bc_name in ['Xmin', 'Xmax']: - bc_n = PT.get_child_from_name(zone_bc_n, bc_name) - PT.new_node('FamilyName', label='FamilyName_t', value=bc_name.upper(), parent=bc_n) - periodic = {'translation' : np.array([1.0, 0, 0], np.float32)} - maia.algo.dist.connect_1to1_families(dist_tree, ('XMIN', 'XMAX'), comm, periodic=periodic, location='Vertex') - assert len(PT.get_nodes_from_label(dist_tree, 'GridConnectivity_t'))!=0 - - # > Periodic adaptation - adpt_dist_tree = maia.algo.dist.adapt_mesh_with_feflo(dist_tree, - 'Metric/metric', - comm, - container_names=['Metric'], - periodic=True, - feflo_opts=f"-c 10 -cmax 10 -p 4") - - adpt_zone = PT.get_all_Zone_t(adpt_dist_tree)[0] - for bc_name in ['Ymin','Ymax','Zmin','Zmax']: - assert PT.get_node_from_name(adpt_zone, bc_name) is not None - assert PT.get_name(adpt_zone) == 'zone' - assert PT.Zone.n_vtx(adpt_zone) != PT.Zone.n_vtx(dist_zone) - adpt_gc = PT.get_node_from_name(adpt_zone, 'Xmin_0') - assert PT.get_value(PT.get_child_from_name(adpt_gc, 'GridConnectivityDonorName')) == 'Xmax_0' \ No newline at end of file diff --git a/maia/algo/dist/test/test_mixed_to_std_elements.py b/maia/algo/dist/test/test_mixed_to_std_elements.py deleted file mode 100644 index e0f93a05..00000000 --- a/maia/algo/dist/test/test_mixed_to_std_elements.py +++ /dev/null @@ -1,283 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np -import os - -import maia.pytree as PT - -from maia.io import file_to_dist_tree -from maia.utils import test_utils as TU - -from maia.algo.dist import convert_elements_to_mixed - -from maia.algo.dist.mixed_to_std_elements import convert_mixed_to_elements, \ - collect_pl_nodes - -@pytest_parallel.mark.parallel([1,2,3]) -def test_collect_pl_nodes(comm): - yaml_path = os.path.join(TU.mesh_dir, 'cube_4.yaml') - dist_tree = file_to_dist_tree(yaml_path, comm) - - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - - pointlist_nodes = collect_pl_nodes(zone) - assert len(pointlist_nodes) == 7 - for pointlist_node in pointlist_nodes: - assert PT.get_name(pointlist_node) == 'PointList' - assert PT.get_label(pointlist_node) == 'IndexArray_t' - - assert PT.get_nodes_from_label(zone, 'PointRange') == [] - - pointlist_nodes = collect_pl_nodes(zone, filter_loc = 'Vertex') - assert len(pointlist_nodes) == 1 - for pointlist_node in pointlist_nodes: - assert PT.get_name(pointlist_node) == 'PointList' - assert PT.get_label(pointlist_node) == 'IndexArray_t' - - pointlist_nodes = collect_pl_nodes(zone, filter_loc = ['Vertex','FaceCenter']) - assert len(pointlist_nodes) == 7 - for pointlist_node in pointlist_nodes: - assert PT.get_name(pointlist_node) == 'PointList' - assert PT.get_label(pointlist_node) == 'IndexArray_t' - - pointlist_nodes = collect_pl_nodes(zone, filter_loc = ['FaceCenter']) - assert len(pointlist_nodes) == 6 - for pointlist_node in pointlist_nodes: - assert PT.get_name(pointlist_node) == 'PointList' - assert PT.get_label(pointlist_node) == 'IndexArray_t' - -@pytest_parallel.mark.parallel([1,2,3]) -def test_convert_mixed_to_elements(comm): - rank = comm.Get_rank() - size = comm.Get_size() - - yaml_path = os.path.join(TU.mesh_dir, 'hex_prism_pyra_tet.yaml') - dist_tree = file_to_dist_tree(yaml_path, comm) - - # Assume this function is already tested - convert_elements_to_mixed(dist_tree, comm) - - convert_mixed_to_elements(dist_tree, comm) - - base = PT.get_node_from_label(dist_tree,'CGNSBase_t') - assert PT.get_name(base) == 'Base' - assert np.all(PT.get_value(base) == [3,3]) - - zone = PT.get_node_from_label(base, 'Zone_t') - assert PT.get_name(zone) == 'Zone' - assert np.all(PT.get_value(zone) == [[11,4,0]]) - - elements_nodes = PT.get_nodes_from_label(zone, 'Elements_t') - assert len(elements_nodes) == 6 - - tetra_node = PT.get_node_from_name(zone, 'Tetra_4') - tetra_er_node = PT.get_node_from_name(tetra_node, 'ElementRange') - tetra_ec_node = PT.get_node_from_name(tetra_node, 'ElementConnectivity') - assert PT.get_value(tetra_node)[0] == 10 - assert np.all(PT.get_value(tetra_er_node) == [1,1]) - - pyra_node = PT.get_node_from_name(zone, 'Pyra_5') - pyra_er_node = PT.get_node_from_name(pyra_node, 'ElementRange') - pyra_ec_node = PT.get_node_from_name(pyra_node, 'ElementConnectivity') - assert PT.get_value(pyra_node)[0] == 12 - assert np.all(PT.get_value(pyra_er_node) == [2,2]) - - penta_node = PT.get_node_from_name(zone, 'Penta_6') - penta_er_node = PT.get_node_from_name(penta_node, 'ElementRange') - penta_ec_node = PT.get_node_from_name(penta_node, 'ElementConnectivity') - assert PT.get_value(penta_node)[0] == 14 - assert np.all(PT.get_value(penta_er_node) == [3,3]) - - hexa_node = PT.get_node_from_name(zone, 'Hexa_8') - hexa_er_node = PT.get_node_from_name(hexa_node, 'ElementRange') - hexa_ec_node = PT.get_node_from_name(hexa_node, 'ElementConnectivity') - assert PT.get_value(hexa_node)[0] == 17 - assert np.all(PT.get_value(hexa_er_node) == [4,4]) - - tri_node = PT.get_node_from_name(zone, 'Tri_3') - tri_er_node = PT.get_node_from_name(tri_node, 'ElementRange') - tri_ec_node = PT.get_node_from_name(tri_node, 'ElementConnectivity') - assert PT.get_value(tri_node)[0] == 5 - assert np.all(PT.get_value(tri_er_node) == [5,10]) - - quad_node = PT.get_node_from_name(zone, 'Quad_4') - quad_er_node = PT.get_node_from_name(quad_node, 'ElementRange') - quad_ec_node = PT.get_node_from_name(quad_node, 'ElementConnectivity') - assert PT.get_value(quad_node)[0] == 7 - assert np.all(PT.get_value(quad_er_node) == [11,16]) - - if rank == 0: - expected_tetra_ec = [ 7, 8, 10, 11] - expected_pyra_ec = [ 6, 7, 10, 9, 11] - expected_penta_ec = [ 2, 3, 5, 7, 8, 10] - expected_hexa_ec = [ 1, 2, 5, 4, 6, 7, 10, 9] - else: - expected_tetra_ec = [] - expected_pyra_ec = [] - expected_penta_ec = [] - expected_hexa_ec = [] - - if size == 1: - expected_tri_ec = [ 6, 11, 9, 8, 10, 11, 6, 7, 11, 7, 8, 11, 2, 5, 3, 9, 11, - 10] - expected_quad_ec = [ 1, 6, 9, 4, 3, 5, 10, 8, 1, 2, 7, 6, 2, 3, 8, 7, 4, - 9, 10, 5, 1, 4, 5, 2] - elif size == 2: - if rank == 0: - expected_tri_ec = [ 6, 11, 9, 8, 10, 11, 6, 7, 11] - expected_quad_ec = [ 1, 6, 9, 4, 3, 5, 10, 8, 1, 2, 7, 6] - elif rank == 1: - expected_tri_ec = [ 7, 8, 11, 2, 5, 3, 9, 11, 10] - expected_quad_ec = [ 2, 3, 8, 7, 4, 9, 10, 5, 1, 4, 5, 2] - elif size == 3: - if rank == 0: - expected_tri_ec = [ 6, 11, 9, 8, 10, 11] - expected_quad_ec = [ 1, 6, 9, 4, 3, 5, 10, 8] - elif rank == 1: - expected_tri_ec = [ 6, 7, 11, 7, 8, 11] - expected_quad_ec = [1, 2, 7, 6, 2, 3, 8, 7] - elif rank == 2: - expected_tri_ec = [ 2, 5, 3, 9, 11, 10] - expected_quad_ec = [ 4, 9, 10, 5, 1, 4, 5, 2] - elif size > 6: - expected_tri_ec_full = [ 6, 11, 9, 8, 10, 11, 6, 7, 11, 7, 8, 11, 2, 5, 3, 9, 11, - 10] - expected_quad_ec_full = [ 1, 6, 9, 4, 3, 5, 10, 8, 1, 2, 7, 6, 2, 3, 8, 7, 4, - 9, 10, 5, 1, 4, 5, 2] - if rank < 6: - expected_tri_ec = expected_tri_ec_full[3*rank:3*(rank+1)] - expected_quad_ec = expected_quad_ec_full[4*rank:4*(rank+1)] - elif rank > 5: - expected_tri_ec = [] - expected_quad_ec = [] - - assert np.all(PT.get_value(tetra_ec_node) == expected_tetra_ec) - assert np.all(PT.get_value(pyra_ec_node) == expected_pyra_ec) - assert np.all(PT.get_value(penta_ec_node) == expected_penta_ec) - assert np.all(PT.get_value(hexa_ec_node) == expected_hexa_ec) - assert np.all(PT.get_value(tri_ec_node) == expected_tri_ec) - assert np.all(PT.get_value(quad_ec_node) == expected_quad_ec) - - bc_xmin = PT.get_nodes_from_predicates(zone, 'ZoneBC_t/BC_t/Xmin')[0] - pointlist_xmin = PT.get_value(PT.get_node_from_name(bc_xmin,'PointList')) - - bc_xmax = PT.get_nodes_from_predicates(zone, 'ZoneBC_t/BC_t/Xmax')[0] - pointlist_xmax = PT.get_value(PT.get_node_from_name(bc_xmax,'PointList')) - - bc_ymin = PT.get_nodes_from_predicates(zone, 'ZoneBC_t/BC_t/Ymin')[0] - pointlist_ymin = PT.get_value(PT.get_node_from_name(bc_ymin,'PointList')) - - bc_ymax = PT.get_nodes_from_predicates(zone, 'ZoneBC_t/BC_t/Ymax')[0] - pointlist_ymax = PT.get_value(PT.get_node_from_name(bc_ymax,'PointList')) - - bc_zmin = PT.get_nodes_from_predicates(zone, 'ZoneBC_t/BC_t/Zmin')[0] - pointlist_zmin = PT.get_value(PT.get_node_from_name(bc_zmin,'PointList')) - - bc_zmax = PT.get_nodes_from_predicates(zone, 'ZoneBC_t/BC_t/Zmax')[0] - pointlist_zmax = PT.get_value(PT.get_node_from_name(bc_zmax,'PointList')) - - if size == 1: - expected_bc_xmin = [[11, 5]] - expected_bc_xmax = [[12, 6]] - expected_bc_zmin = [[16, 9]] - else: - if rank == 0: - expected_bc_xmin = [[11]] - expected_bc_xmax = [[12]] - expected_bc_zmin = [[16]] - elif rank == 1: - expected_bc_xmin = [[5]] - expected_bc_xmax = [[6]] - expected_bc_zmin = [[9]] - else: - expected_bc_xmin = [] - expected_bc_xmax = [] - expected_bc_zmin = [] - - if size == 1: - expected_bc_ymin = [[13, 14, 7, 8]] - elif size == 2: - if rank == 0: - expected_bc_ymin = [[13, 14]] - elif rank == 1: - expected_bc_ymin = [[7, 8]] - elif size == 3: - if rank == 0: - expected_bc_ymin = [[13, 14]] - elif rank == 1: - expected_bc_ymin = [[7]] - elif rank == 2: - expected_bc_ymin = [[8]] - else: - if rank == 0: - expected_bc_ymin = [[13]] - elif rank == 1: - expected_bc_ymin = [[14]] - elif rank == 2: - expected_bc_ymin = [[7]] - elif rank == 3: - expected_bc_ymin = [[8]] - else: - expected_bc_ymin = [] - - if rank == 0: - expected_bc_ymax = [[15]] - expected_bc_zmax = [[10]] - else: - expected_bc_ymax = [] - expected_bc_zmax = [] - - assert np.all(pointlist_xmin == expected_bc_xmin) - assert np.all(pointlist_xmax == expected_bc_xmax) - assert np.all(pointlist_ymin == expected_bc_ymin) - assert np.all(pointlist_ymax == expected_bc_ymax) - assert np.all(pointlist_zmin == expected_bc_zmin) - assert np.all(pointlist_zmax == expected_bc_zmax) - - - if size == 1: - expected_data = [10, 12, 14, 17] - - elif size == 2: - if rank == 0: - expected_data = [10, 12] - elif rank == 1: - expected_data = [14, 17] - - elif size == 3: - if rank == 0: - expected_data = [10, 12] - elif rank == 1: - expected_data = [14] - elif rank == 2: - expected_data = [17] - - elif size == 4: - if rank == 0: - expected_data = [10] - elif rank == 1: - expected_data = [12] - elif rank == 2: - expected_data = [14] - elif rank == 3: - expected_data = [17] - - else: - if rank == 0: - expected_data = [10] - elif rank == 1: - expected_data = [12] - elif rank == 2: - expected_data = [14] - elif rank == 3: - expected_data = [17] - else: - expected_data = [] - - fs = PT.get_child_from_label(zone, 'FlowSolution_t') - assert PT.get_name(fs) == 'FlowSolution' - - data = PT.get_child_from_label(fs, 'DataArray_t') - assert PT.get_name(data) == 'TypeElements3D' - assert np.all(PT.get_value(data) == expected_data) - diff --git a/maia/algo/dist/test/test_redistribute_tree.py b/maia/algo/dist/test/test_redistribute_tree.py deleted file mode 100644 index bb8f1ed5..00000000 --- a/maia/algo/dist/test/test_redistribute_tree.py +++ /dev/null @@ -1,417 +0,0 @@ -import pytest_parallel -import pytest -import os -import numpy as np - -import maia -import maia.io as Mio -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import par_utils -from maia.utils import test_utils as TU -from maia.algo.dist import redistribute as RDT -from maia.pytree.yaml import parse_yaml_cgns - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -dtype = 'I4' if pdm_gnum_dtype == np.int32 else 'I8' - -# ======================================================================================= -# --------------------------------------------------------------------------------------- - -distribution = lambda n_elt, comm : par_utils.gathering_distribution(0, n_elt, comm) - -# --------------------------------------------------------------------------------------- -@pytest_parallel.mark.parallel([3]) -def test_redistribute_pl_node_U(comm): - if comm.Get_rank() == 0: - yt_bc = f""" - BC0 BC_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[1, 5, 7, 12]]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [0, 4, 11]: - """ - if comm.Get_rank() == 1: - yt_bc = f""" - BC0 BC_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[8, 11, 10, 21]]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [4, 8, 11]: - """ - if comm.Get_rank() == 2: - yt_bc = f""" - BC0 BC_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[22, 23, 30]]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [8, 11, 11]: - """ - - dist_bc = parse_yaml_cgns.to_node(yt_bc) - - RDT.redistribute_pl_node(dist_bc, distribution, comm) - - if comm.Get_rank()==0: - assert np.array_equal(PT.get_node_from_name(dist_bc, 'Index' )[1], np.array([0,11,11])) - assert np.array_equal(PT.get_node_from_name(dist_bc, 'PointList')[1], np.array([[1, 5, 7, 12, 8, 11, 10, 21, 22, 23, 30]])) - - else: - assert np.array_equal(PT.get_node_from_name(dist_bc, 'Index' )[1], np.array([11,11,11])) - assert PT.get_node_from_name(dist_bc, 'PointList')[1].size==0 -# --------------------------------------------------------------------------------------- - -# --------------------------------------------------------------------------------------- -@pytest_parallel.mark.parallel([3]) -def test_redistribute_pl_node_S(comm): - - distri_in_full = np.array([0, 5, 5, 5]) - distri_in = par_utils.full_to_partial_distribution(distri_in_full, comm) - if comm.Get_rank() == 0: - pl = np.array([[1,2,3,4,5], [10,20,30,40,50], [100,200,300,400,500]], np.int32) - else: - pl = np.ones((3, 0), np.int32) - - bc = PT.new_BC('BC', point_list=pl) - MT.newDistribution({'Index': distri_in}, bc) - - RDT.redistribute_pl_node(bc, par_utils.uniform_distribution, comm) - - distri_out_expt = par_utils.full_to_partial_distribution(np.array([0, 2, 4, 5]), comm) - assert (MT.getDistribution(bc, 'Index')[1] == distri_out_expt).all() - if comm.Get_rank() == 0: - assert (PT.get_child_from_name(bc, 'PointList')[1] == [[1,2], [10,20], [100,200]]).all() - elif comm.Get_rank() == 1: - assert (PT.get_child_from_name(bc, 'PointList')[1] == [[3,4], [30,40], [300,400]]).all() - elif comm.Get_rank() == 2: - assert (PT.get_child_from_name(bc, 'PointList')[1] == [[5], [50], [500]]).all() -# --------------------------------------------------------------------------------------- - -# --------------------------------------------------------------------------------------- -@pytest_parallel.mark.parallel([3]) -def test_redistribute_data_node_U(comm): - if comm.Get_rank() == 0: - yt_fs = f""" - FlowSolution FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - Density DataArray_t R8 [0.9, 1.1]: - MomentumX DataArray_t R8 [0.0, 1.0]: - """ - old_distrib = np.array([0,2,6]) - new_distrib = np.array([0,6,6]) - if comm.Get_rank() == 1: - yt_fs = f""" - FlowSolution FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - Density DataArray_t R8 [0.8, 1.2]: - MomentumX DataArray_t R8 [0.1, 0.9]: - """ - old_distrib = np.array([2,4,6]) - new_distrib = np.array([6,6,6]) - if comm.Get_rank() == 2: - yt_fs = f""" - FlowSolution FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - Density DataArray_t R8 [0.7, 1.3]: - MomentumX DataArray_t R8 [0.2, 0.8]: - """ - old_distrib = np.array([4,6,6]) - new_distrib = np.array([6,6,6]) - - dist_fs = parse_yaml_cgns.to_node(yt_fs) - - RDT.redistribute_data_node(dist_fs, old_distrib, new_distrib, comm) - - if comm.Get_rank()==0: - assert np.array_equal(PT.get_node_from_name(dist_fs, 'Density')[1], - np.array([0.9, 1.1, 0.8, 1.2, 0.7, 1.3])) - assert np.array_equal(PT.get_node_from_name(dist_fs, 'MomentumX')[1], - np.array([0.0, 1.0, 0.1, 0.9, 0.2, 0.8])) - - else: - assert PT.get_node_from_name(dist_fs, 'Density' )[1].size == 0 - assert PT.get_node_from_name(dist_fs, 'MomentumX')[1].size == 0 -# --------------------------------------------------------------------------------------- - -# --------------------------------------------------------------------------------------- -@pytest.mark.parametrize("elt", ["NGON_n", 'TRI_3']) -@pytest_parallel.mark.parallel([3]) -def test_redistribute_elements_node_U(elt, comm): - cgns_elt = {'NGON_n': 22, 'TRI_3': 5} - if comm.Get_rank() == 0: - str_ESO = {'TRI_3' :'', - 'NGON_n':'ElementStartOffset DataArray_t I4 [0, 3, 6, 9]:' } - str_DEC = {'TRI_3' : '', - 'NGON_n':f'ElementConnectivity DataArray_t {dtype} [0, 9, 24]:' } - yt_bc = f""" - Elements Elements_t I4 [{cgns_elt[elt]}, 0]: - ElementRange IndexRange_t I4 [1, 8]: - {str_ESO[elt]} - ElementConnectivity DataArray_t I4 [1, 3, 7, 2, 9, 2, 4, 2, 7]: - ParentElements DataArray_t I4 [[1, 4], [9, 2], [9, 4]]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [0, 3, 8]: - {str_DEC[elt]} - """ - - if comm.Get_rank() == 1: - str_ESO = {'TRI_3' :'', - 'NGON_n':'ElementStartOffset DataArray_t I4 [9, 12, 15, 18]:' } - str_DEC = {'TRI_3' : '', - 'NGON_n':f'ElementConnectivity DataArray_t {dtype} [ 9, 18, 24]:' } - yt_bc = f""" - Elements Elements_t I4 [{cgns_elt[elt]}, 0]: - ElementRange IndexRange_t I4 [1, 8]: - {str_ESO[elt]} - ElementConnectivity DataArray_t I4 [7, 8, 3, 6, 1, 4, 2, 6, 5]: - ParentElements DataArray_t I4 [[2, 5], [1, 2], [7, 6]]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [ 3, 6, 8]: - {str_DEC[elt]} - """ - if comm.Get_rank() == 2: - str_ESO = {'TRI_3' :'', - 'NGON_n':'ElementStartOffset DataArray_t I4 [18, 21, 24]:' } - str_DEC = {'TRI_3' : '', - 'NGON_n':f'ElementConnectivity DataArray_t {dtype} [18, 24, 24]:' } - yt_bc = f""" - Elements Elements_t I4 [{cgns_elt[elt]}, 0]: - ElementRange IndexRange_t I4 [1, 8]: - {str_ESO[elt]} - ElementConnectivity DataArray_t I4 [9, 2, 3, 1, 5, 4]: - ParentElements DataArray_t I4 [[8, 1], [9, 2]]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [ 6, 8, 8]: - {str_DEC[elt]} - """ - - dist_elt = parse_yaml_cgns.to_node(yt_bc) - - RDT.redistribute_elements_node(dist_elt, distribution, comm) - - assert np.array_equal(PT.Element.Range(dist_elt), np.array([1, 8])) - - if comm.Get_rank() == 0: - expt_node = {'ElementConnectivity' : np.array([1, 3, 7, 2, 9, 2, 4, 2, 7, 7, 8, 3, 6, 1, 4, 2, 6, 5, 9, 2, 3, 1, 5, 4]), - 'ElementStartOffset' : np.array([0, 3, 6, 9, 12, 15, 18, 21, 24]), - 'ParentElements' : np.array([ [1, 4], [9, 2], [9, 4], [2, 5], [1, 2], [7, 6], [8, 1], [9, 2]]), - ':CGNS#Distribution/Element' : np.array([0, 8, 8 ]), - ':CGNS#Distribution/ElementConnectivity' : np.array([0, 24, 24 ]), - } - - for path, expt_value in expt_node.items(): - if path in ['ElementStartOffset', ':CGNS#Distribution/ElementConnectivity']: - if elt == 'NGON_n': - assert np.array_equal(PT.get_node_from_path(dist_elt, path)[1], expt_value) - else: - assert PT.get_node_from_path(dist_elt, path) is None - else: - assert np.array_equal(PT.get_node_from_path(dist_elt, path)[1], expt_value) - - else: - assert PT.get_child_from_name(dist_elt, 'ElementConnectivity')[1].size == 0 - assert PT.get_child_from_name(dist_elt, 'ParentElements' )[1].size == 0 - assert np.array_equal(PT.get_node_from_path(dist_elt, ':CGNS#Distribution/Element')[1], - np.array([8, 8, 8 ])) - if elt == 'NGON_n': - assert PT.get_child_from_name(dist_elt, 'ElementStartOffset' )[1].size == 1 - assert np.array_equal(PT.get_node_from_path(dist_elt, ':CGNS#Distribution/ElementConnectivity')[1], - np.array([24, 24, 24 ])) - else : - assert PT.get_node_from_name(dist_elt, 'ElementStartOffset' ) is None - assert PT.get_node_from_name(dist_elt, ':CGNS#Distribution/ElementConnectivity') is None -# --------------------------------------------------------------------------------------- - -# --------------------------------------------------------------------------------------- -@pytest_parallel.mark.parallel([2]) -def test_redistribute_mixed_elements_node_U(comm): - - if comm.Get_rank() == 0: - yt = f""" - Mixed Elements_t I4 [20, 0]: - ElementRange IndexRange_t I4 [1, 8]: - ElementStartOffset DataArray_t I4 [0, 3, 8, 12]: - ElementConnectivity DataArray_t I4 [1, 3, 7, 2, 9, 2, 4, 2, 7, 1, 3, 4]: - ParentElements DataArray_t I4 [[1, 4], [9, 2], [9, 4]]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [0, 3, 6]: - ElementConnectivity DataArray_t {dtype} [0, 12, 24]: - """ - - if comm.Get_rank() == 1: - yt = f""" - Mixed Elements_t I4 [20, 0]: - ElementRange IndexRange_t I4 [1, 8]: - ElementStartOffset DataArray_t I4 [12, 15, 19, 24]: - ElementConnectivity DataArray_t I4 [7, 8, 3, 6, 1, 4, 2, 6, 5, 6, 3, 1]: - ParentElements DataArray_t I4 [[2, 5], [1, 2], [7, 6]]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [ 3, 6, 6]: - ElementConnectivity DataArray_t {dtype} [12, 24, 24]: - """ - - dist_elt = parse_yaml_cgns.to_node(yt) - - RDT.redistribute_elements_node(dist_elt, distribution, comm) - - assert np.array_equal(PT.Element.Range(dist_elt), np.array([1, 8])) - - if comm.Get_rank()==0: - expt_value = {'ElementConnectivity' : np.array([1, 3, 7, 2, 9, 2, 4, 2, 7, 1, 3, 4, 7, 8, 3, 6, 1, 4, 2, 6, 5, 6, 3, 1]), - 'ElementStartOffset' : np.array([0, 3, 8, 12, 15, 19, 24]), - 'ParentElements' : np.array([ [1, 4], [9, 2], [9, 4], [2, 5], [1, 2], [7, 6]]), - ':CGNS#Distribution/Element' : np.array([0, 6, 6]), - ':CGNS#Distribution/ElementConnectivity' : np.array([0, 24, 24]), - } - - for path,expt_value in expt_value.items(): - assert np.array_equal(PT.get_node_from_path(dist_elt, path)[1], expt_value) - - else: - assert PT.get_child_from_name(dist_elt, 'ElementConnectivity')[1].size == 0 - assert PT.get_child_from_name(dist_elt, 'ParentElements' )[1].size == 0 - assert np.array_equal(PT.get_node_from_path(dist_elt, ':CGNS#Distribution/Element')[1], - np.array([6, 6, 6])) - assert PT.get_child_from_name(dist_elt, 'ElementStartOffset' )[1].size == 1 - assert np.array_equal(PT.get_node_from_path(dist_elt, ':CGNS#Distribution/ElementConnectivity')[1], - np.array([24, 24, 24])) -# --------------------------------------------------------------------------------------- - -# --------------------------------------------------------------------------------------- -@pytest_parallel.mark.parallel([2]) -def test_redistribute_gc_node_U(comm): - if comm.Get_rank() == 0: - yt_gc = f""" - ZGC ZoneGridConnectivity_t: - Zmin_match GridConnectivity_t 'zone': - GridConnectivityType GridConnectivityType_t 'Abutting1to1': - GridLocation GridLocation_t 'FaceCenter': - PointList IndexArray_t I4 [[]]: - PointListDonor IndexArray_t I4 [[]]: - GridConnectivityProperty GridConnectivityProperty_t: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [0, 0, 4]: - Zmax_match GridConnectivity_t 'zone': - GridConnectivityType GridConnectivityType_t 'Abutting1to1': - GridLocation GridLocation_t 'FaceCenter': - PointList IndexArray_t I4 [[9, 10, 11, 12]]: - PointListDonor IndexArray_t I4 [[1, 2, 3, 4]]: - GridConnectivityProperty GridConnectivityProperty_t: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [0, 4, 4]: - """ - if comm.Get_rank() == 1: - yt_gc = f""" - ZGC ZoneGridConnectivity_t: - Zmin_match GridConnectivity_t 'zone': - GridConnectivityType GridConnectivityType_t 'Abutting1to1': - GridLocation GridLocation_t 'FaceCenter': - PointList IndexArray_t I4 [[1, 2, 3, 4]]: - PointListDonor IndexArray_t I4 [[9, 10, 11, 12]]: - GridConnectivityProperty GridConnectivityProperty_t: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [0, 4, 4]: - Zmax_match GridConnectivity_t 'zone': - GridConnectivityType GridConnectivityType_t 'Abutting1to1': - GridLocation GridLocation_t 'FaceCenter': - PointList IndexArray_t I4 [[]]: - PointListDonor IndexArray_t I4 [[]]: - GridConnectivityProperty GridConnectivityProperty_t: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [4, 4, 4]: - """ - - zgc_n = parse_yaml_cgns.to_node(yt_gc) - - for gc_n in PT.get_children_from_label(zgc_n, 'GridConnectivity_t') : - RDT.redistribute_pl_node(gc_n, distribution, comm) - - if comm.Get_rank()==0: - if gc_n[0]=='Zmin_match': - assert np.array_equal(PT.get_node_from_path(gc_n, 'PointList' )[1], np.array([[1, 2, 3, 4]])) - assert np.array_equal(PT.get_node_from_path(gc_n, 'PointListDonor')[1], np.array([[9, 10, 11, 12]])) - elif gc_n[0]=='Zmax_match': - assert np.array_equal(PT.get_node_from_path(gc_n, 'PointList' )[1], np.array([[9, 10, 11, 12]])) - assert np.array_equal(PT.get_node_from_path(gc_n, 'PointListDonor')[1], np.array([[1, 2, 3, 4]])) - assert np.array_equal(PT.get_node_from_path(gc_n, ':CGNS#Distribution/Index')[1], np.array([0,4,4])) - - else: - assert PT.get_node_from_name(gc_n, 'PointList')[1].size==0 - assert np.array_equal(PT.get_node_from_path(gc_n, ':CGNS#Distribution/Index')[1], np.array([4,4,4])) -# --------------------------------------------------------------------------------------- - -# --------------------------------------------------------------------------------------- -@pytest_parallel.mark.parallel([1, 2, 3]) -def test_redistribute_zone_U(comm): - - # Reference directory and file - ref_file = os.path.join(TU.mesh_dir, 'cube_bcdataset_and_periodic.yaml') - dist_tree = Mio.file_to_dist_tree(ref_file, comm) - - for zone in PT.get_all_Zone_t(dist_tree): - distribution = lambda n_elt, comm : par_utils.gathering_distribution(0, n_elt, comm) - RDT.redistribute_zone(zone, distribution, comm) - - if comm.Get_rank() == 0: - distri_to_check = {'Base/zone/NGonElements/:CGNS#Distribution/Element' : np.array([ 0, 36, 36]), - 'Base/zone/NGonElements/:CGNS#Distribution/ElementConnectivity' : np.array([ 0, 144, 144]), - 'Base/zone/NFaceElements/:CGNS#Distribution/Element' : np.array([ 0, 8, 8]), - 'Base/zone/NFaceElements/:CGNS#Distribution/ElementConnectivity' : np.array([ 0, 48, 48]), - 'Base/zone/:CGNS#Distribution/Vertex' : np.array([ 0, 27, 27]), - 'Base/zone/:CGNS#Distribution/Cell' : np.array([ 0, 8, 8]), - 'Base/zone/ZoneBC/Xmin/:CGNS#Distribution/Index' : np.array([ 0, 4, 4]), - 'Base/zone/ZoneBC/Xmax/:CGNS#Distribution/Index' : np.array([ 0, 4, 4]), - 'Base/zone/ZoneBC/Ymin/:CGNS#Distribution/Index' : np.array([ 0, 4, 4]), - 'Base/zone/ZoneBC/Ymax/:CGNS#Distribution/Index' : np.array([ 0, 4, 4]), - 'Base/zone/ZoneGridConnectivity/Zmin_match/:CGNS#Distribution/Index' : np.array([ 0, 4, 4]), - 'Base/zone/ZoneGridConnectivity/Zmin_match/:CGNS#Distribution/Index' : np.array([ 0, 4, 4]) - } - # Check distribution on full ranks - for key,value in distri_to_check.items(): - assert np.array_equal(PT.get_node_from_path(dist_tree, key)[1], value) - - ref_tree = Mio.read_tree(ref_file) - # file_to_dist_tree read with pdm dtype conversion, so do it for the reference - maia.io.fix_tree._enforce_pdm_dtype(ref_tree) - PT.rm_nodes_from_name(dist_tree, ':CGNS#Distribution') - - assert PT.is_same_tree(ref_tree[2][1], dist_tree[2][1]) - - - else : - distri_to_check = {'Base/zone/NGonElements/:CGNS#Distribution/Element' : np.array([ 36, 36, 36]), - 'Base/zone/NGonElements/:CGNS#Distribution/ElementConnectivity' : np.array([144, 144, 144]), - 'Base/zone/NFaceElements/:CGNS#Distribution/Element' : np.array([ 8, 8, 8]), - 'Base/zone/NFaceElements/:CGNS#Distribution/ElementConnectivity' : np.array([ 48, 48, 48]), - 'Base/zone/:CGNS#Distribution/Vertex' : np.array([ 27, 27, 27]), - 'Base/zone/:CGNS#Distribution/Cell' : np.array([ 8, 8, 8]), - 'Base/zone/ZoneBC/Xmin/:CGNS#Distribution/Index' : np.array([ 4, 4, 4]), - 'Base/zone/ZoneBC/Xmax/:CGNS#Distribution/Index' : np.array([ 4, 4, 4]), - 'Base/zone/ZoneBC/Ymin/:CGNS#Distribution/Index' : np.array([ 4, 4, 4]), - 'Base/zone/ZoneBC/Ymax/:CGNS#Distribution/Index' : np.array([ 4, 4, 4]), - 'Base/zone/ZoneGridConnectivity/Zmin_match/:CGNS#Distribution/Index' : np.array([ 4, 4, 4]), - 'Base/zone/ZoneGridConnectivity/Zmin_match/:CGNS#Distribution/Index' : np.array([ 4, 4, 4]) - } - # Check distribution on empty ranks - for key,value in distri_to_check.items(): - assert np.array_equal(PT.get_node_from_path(dist_tree, key)[1], value) - - # Cleaning distribution and GridConnectivityProperty nodes to check arrays - PT.rm_nodes_from_name(dist_tree, ':CGNS#Distribution') - PT.rm_nodes_from_name(dist_tree, 'GridConnectivityProperty') - - # Check arrays - for node in PT.get_nodes_from_label(zone, 'IndexArray_t'): - assert node[1].size==0 - - for node in PT.get_nodes_from_label(zone, 'DataArray_t'): - if PT.get_name(node) == 'ElementStartOffset': - assert node[1].size==1 - else: - assert node[1].size==0 -# --------------------------------------------------------------------------------------- - - - -# ======================================================================================= diff --git a/maia/algo/dist/test/test_remove_element.py b/maia/algo/dist/test/test_remove_element.py deleted file mode 100644 index f53d9182..00000000 --- a/maia/algo/dist/test/test_remove_element.py +++ /dev/null @@ -1,250 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia -import maia.pytree as PT - -from maia.pytree.yaml import parse_yaml_cgns -from maia.utils import par_utils - -from maia.algo.dist import remove_element as RME - -def test_remove_element(): - zone = PT.new_Zone() - - quad_ec = [1,2,6,5,2,3,7,6,3,4,8,7,5,6,10,9,6,7,11,10,7,8,12,11] - quad = PT.new_Elements('Quad', 'QUAD_4', econn=quad_ec, erange=[1,6], parent=zone) - bar = PT.new_Elements('Bar', 'BAR_2', econn=[10,9,11,10,12,11,5,1,9,5], erange=[7,11], parent=zone) - - ngon_ec = [2,1,3,2,1,5,4,3,2,6,3,7,6,5,8,4,6,7,5,9,8,7,10,6,7,11,9,10,12,8,10,11,11,12] - ngon_pe = np.array([[29,0],[30,0],[29,0],[31,0],[29,30],[30,31],[29,32],[31,0],[33,30],\ - [32,0],[31,34],[33,32],[33,34],[32,0],[34,0],[33,0],[34,0]]) - expected_pe = np.copy(ngon_pe) - expected_pe[np.where(ngon_pe > 0)] -= 5 - ngon = PT.new_NGonElements('NGon', parent=zone, - erange=[12,28], eso=np.arange(0, 35, 2), ec=ngon_ec, pe=ngon_pe) - - nface_ec = [7,5,1,3,6,2,5,9,11,4,6,8,7,10,12,14,13,12,9,16,11,13,15,17] - nface = PT.new_NFaceElements('NFace', erange=[29,34], eso=np.arange(0,24+1,4), ec=nface_ec, parent=zone) - - zbc = PT.new_ZoneBC(zone) - bc = PT.new_BC(point_list = [[25,27,28]], loc='EdgeCenter', parent=zbc) - - RME.remove_element(zone, bar) - assert (PT.Element.Range(quad) == [1,6]).all() - assert (PT.Element.Range(ngon) == [12-5,28-5]).all() - assert (PT.Element.Range(nface) == [29-5,34-5]).all() - assert (PT.get_child_from_name(ngon, 'ParentElements')[1] == expected_pe).all() - assert (PT.get_node_from_name(zone, 'PointList')[1] == [[20,22,23]]).all() - assert PT.get_node_from_name(zone, 'Bar') is None - -@pytest_parallel.mark.parallel(1) -def test_remove_ngons(comm): - #Generated from G.cartNGon((0,0,0), (1,1,0), (3,4,1)) - # TODO handwritten ngon (4_cubes?) - - ec = [1,4, 2,5, 3,6, 4,7, 5,8, 6,9, 7,10, 8,11, 9,12, 1,2, 2,3, 4,5, 5,6, 7,8, 8,9, 10,11, 11,12] - pe = np.array([[18,0], [18,19], [19,0], [20,0], [20,21], [21,0], [22,0], [22,23], [23,0], - [18,0], [19,0], [18,20], [19,21], [20,22], [21,23], [22,0], [23,0]]) - ngon = PT.new_NGonElements('NGon', erange=[1,17], eso=np.arange(0, 35, 2), ec=ec, pe=pe) - distri = PT.new_child(ngon, ':CGNS#Distribution', 'UserDefinedData_t') - PT.new_DataArray('Element', [0, 17, 17], parent=distri) - PT.new_DataArray('ElementConnectivity', [0, 34, 34], parent=distri) - - RME.remove_ngons(ngon, [1,15], comm) - - expected_ec = [1,4, 3,6, 4,7, 5,8, 6,9, 7,10, 8,11, 9,12, 1,2, 2,3, 4,5, 5,6, 7,8, 8,9, 11,12] - expected_pe = np.array([[16,0], [17,0], [18,0], [18,19], [19,0], [20,0], [20,21], [21,0], - [16,0], [17,0], [16,18], [17,19], [18,20], [19,21], [21,0]]) - assert (PT.get_node_from_name(ngon, 'ElementRange')[1] == [1, 15]).all() - assert (PT.get_node_from_name(ngon, 'ElementConnectivity')[1] == expected_ec).all() - assert (PT.get_node_from_name(ngon, 'ParentElements')[1] == expected_pe).all() - assert (PT.get_node_from_name(ngon, 'ElementStartOffset')[1] == np.arange(0,31,2)).all() - assert (PT.get_node_from_path(ngon, ':CGNS#Distribution/Element')[1] == [0,15,15]).all() - assert (PT.get_node_from_path(ngon, ':CGNS#Distribution/ElementConnectivity')[1] == [0,30,30]).all() - -@pytest_parallel.mark.parallel(2) -def test_remove_ngons_2p(comm): - - #Generated from G.cartNGon((0,0,0), (1,1,0), (3,4,1)) - - if comm.Get_rank() == 0: - ec = [1,4,2,5,3,6,4,7,5,8] - pe = np.array([[1,0], [1,2], [2,0], [3,0], [3,4]]) - eso = np.arange(0,2*5+1,2) - distri_e = [0, 5, 17] - distri_ec = [0, 10, 34] - to_remove = [2-1] - - expected_distri_e = [0, 4, 15] - expected_ec = [1,4, 3,6,4,7,5,8] - expected_pe = np.array([[1,0], [2,0], [3,0], [3,4]]) - expected_eso = np.arange(0, 2*4+1, 2) - elif comm.Get_rank() == 1: - ec = [6,9,7,10,8,11,9,12,1,2,2,3,4,5,5,6,7,8,8,9,10,11,11,12] - pe = np.array([[4,0], [5,0], [5,6], [6,0], [1,0], [2,0], [1,3], [2,4], [3,5], [4,6], [5,0], [6,0]]) - eso = np.arange(10, 2*17+1,2) - distri_e = [5, 17, 17] - distri_ec = [10, 34, 34] - to_remove = [16-5-1] - - expected_distri_e = [4, 15, 15] - expected_ec = [6,9,7,10,8,11,9,12,1,2,2,3,4,5,5,6,7,8,8,9, 11,12] - expected_pe = np.array([[4,0], [5,0], [5,6], [6,0], [1,0], [2,0], [1,3], [2,4], [3,5], [4,6], [6,0]]) - expected_eso = np.arange(8, 2*15+1, 2) - - ngon = PT.new_NGonElements('NGon', erange=[7,24], eso=eso, ec=ec, pe=pe) - distri = PT.new_child(ngon, ':CGNS#Distribution', 'UserDefinedData_t') - PT.new_DataArray('Element', distri_e, parent=distri) - PT.new_DataArray('ElementConnectivity', distri_ec, parent=distri) - - RME.remove_ngons(ngon, to_remove, comm) - - assert (PT.get_node_from_name(ngon, 'ElementRange')[1] == [7, 24-2]).all() - assert (PT.get_node_from_name(ngon, 'ElementConnectivity')[1] == expected_ec).all() - assert (PT.get_node_from_name(ngon, 'ParentElements')[1] == expected_pe).all() - assert (PT.get_node_from_name(ngon, 'ElementStartOffset')[1] == expected_eso).all() - assert (PT.get_node_from_path(ngon, ':CGNS#Distribution/Element')[1] == expected_distri_e).all() - -@pytest.mark.parametrize('elt_name',['BAR_2','TRI_3','TETRA_4']) -@pytest_parallel.mark.parallel([2]) -def test_remove_elts_from_pl(elt_name, comm): - - dist_tree = maia.factory.dcube_generator.dcube_nodal_generate(3, 1., [0.,0.,0.], 'TETRA_4', comm, get_ridges=True) - dist_zone = PT.get_node_from_label(dist_tree, 'Zone_t') - - # > Define lineic BC - ridge_pl_f = np.arange(89, 113, dtype=np.int32) - ridge_distri = par_utils.uniform_distribution(ridge_pl_f.size, comm) - ridge_pl = ridge_pl_f[ridge_distri[0] : ridge_distri[1]] - - zone_bc_n = PT.get_node_from_label(dist_tree, 'ZoneBC_t') - bc_n = PT.new_BC('ridges', point_list=ridge_pl.reshape((1,-1), order='F'), loc='EdgeCenter', parent=zone_bc_n) - PT.maia.newDistribution({'Index':ridge_distri}, parent=bc_n) - - # > Define elements to remove - is_asked_elt = lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)==elt_name - elt_n = PT.get_child_from_predicate(dist_zone, is_asked_elt) - if elt_name=='TETRA_4': - elt_pl_f = np.array([1,13,2,25,14,37], dtype=np.int32)+1 - elif elt_name=='TRI_3': - elt_pl_f = np.arange(41, 51, dtype=np.int32) - elif elt_name=='BAR_2': - elt_pl_f = np.arange(89, 113, dtype=np.int32) - start, end, _ = par_utils.uniform_distribution(elt_pl_f.size, comm) - elt_pl = elt_pl_f[start:end] - - RME.remove_elts_from_pl(dist_zone, elt_n, elt_pl, comm) - - # > Checking result - n_tet = {'TETRA_4':34, 'TRI_3':40, 'BAR_2':40} - n_tri = {'TETRA_4':48, 'TRI_3':38, 'BAR_2':48} - n_bar = {'TETRA_4':24, 'TRI_3':24, 'BAR_2': 0} - - is_tet_elt = lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TETRA_4' - is_tri_elt = lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='TRI_3' - is_bar_elt = lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='BAR_2' - is_tri_bc = lambda n: PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n)=='FaceCenter' - is_bar_bc = lambda n: PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n)=='EdgeCenter' - - elt_n = PT.get_child_from_predicate(dist_zone, is_tet_elt) - elt_distrib = PT.maia.getDistribution(elt_n, 'Element')[1] - n_elt = elt_distrib[1]-elt_distrib[0] - elt_er = PT.get_child_from_name(elt_n,'ElementRange')[1] - assert np.array_equal(elt_er, np.array([1, n_tet[elt_name]])) - assert PT.get_child_from_name(elt_n,'ElementConnectivity')[1].size==n_elt*4 - assert elt_distrib[2]==n_tet[elt_name] - - cell_distri = par_utils.dn_to_distribution(n_elt, comm) - assert np.array_equal(PT.maia.getDistribution(dist_zone, 'Cell')[1], cell_distri) - - elt_n = PT.get_child_from_predicate(dist_zone, is_tri_elt) - elt_er = PT.get_child_from_name(elt_n,'ElementRange')[1] - elt_distrib = PT.maia.getDistribution(elt_n, 'Element')[1] - n_elt = elt_distrib[1]-elt_distrib[0] - expected_er = np.array([n_tet[elt_name]+1, n_tet[elt_name]+n_tri[elt_name]]) - assert np.array_equal(elt_er, expected_er) - assert PT.get_child_from_name(elt_n,'ElementConnectivity')[1].size==n_elt*3 - assert elt_distrib[2]==n_tri[elt_name] - for bc_n in PT.get_nodes_from_predicate(dist_zone, is_tri_bc): - pl = PT.Subset.getPatch(bc_n)[1] - assert elt_er[0]<=np.min(pl) and np.max(pl)<=elt_er[1] - if elt_name=='TRI_3': - assert PT.get_node_from_name_and_label(dist_zone, 'Zmin', 'BC_t') is None - zmax_bc_n = PT.get_node_from_name_and_label(dist_zone, 'Zmax', 'BC_t') - assert PT.maia.getDistribution(zmax_bc_n, 'Index')[1][2]==6 - - if elt_name=='BAR_2': - assert PT.get_child_from_name(dist_zone, 'BAR_2') is None - assert len(PT.get_nodes_from_predicate(dist_zone, is_bar_bc))==0 - else: - elt_n = PT.get_child_from_predicate(dist_zone, is_bar_elt) - elt_er = PT.get_child_from_name(elt_n,'ElementRange')[1] - elt_distrib = PT.maia.getDistribution(elt_n, 'Element')[1] - n_elt = elt_distrib[1]-elt_distrib[0] - expected_er = np.array([n_tet[elt_name]+n_tri[elt_name]+1, - n_tet[elt_name]+n_tri[elt_name]+n_bar[elt_name]]) - assert np.array_equal(elt_er, expected_er) - assert PT.get_child_from_name(elt_n,'ElementConnectivity')[1].size==n_elt*2 - assert elt_distrib[2]==n_bar[elt_name] - - -@pytest_parallel.mark.parallel(2) -def test_remove_elts_from_pl_conflict_bc(comm): - rank = comm.Get_rank() - cell_dn = ['[0,1,1]' ,'[1,1,1]' ][rank] - tri1_ec = ['[1,2,3,3,2,1]','[4,5,6]' ][rank] - tri2_ec = ['[7,8,9,9,8,7]','[10,11,12]'][rank] - tri_dn = ['[0,2,3]' ,'[2,3,3]' ][rank] - bc_pl = ['[2,3]' ,'[6]' ][rank] - bc_dn = ['[0,2,3]' ,'[2,3,3]' ][rank] - - dist_zone = parse_yaml_cgns.to_node(f""" - Zone Zone_t: - ZoneType ZoneType_t 'Unstructured': - :CGNS#Distribution UserDefinedData_t: - Cell DataArray_t {cell_dn}: - TRI_1 Elements_t I4 [5, 0]: - ElementRange IndexRange_t I4 [1, 3]: - ElementConnectivity DataArray_t I4 {tri1_ec}: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {tri_dn}: - TRI_2 Elements_t I4 [5, 0]: - ElementRange IndexRange_t I4 [4, 6]: - ElementConnectivity DataArray_t I4 {tri2_ec}: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {tri_dn}: - TETRA Elements_t I4 [10, 0]: - ElementRange IndexRange_t I4 [7, 7]: - ZoneBC ZoneBC_t: - BC BC_t 'Null': - GridLocation GridLocation_t 'FaceCenter': - PointList IndexArray_t I4 [{bc_pl}]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {bc_dn}: - """) - - elt_n = PT.get_child_from_name(dist_zone, 'TRI_1') - elt_pl = np.array([rank+1], dtype=np.int32) - RME.remove_elts_from_pl(dist_zone, elt_n, elt_pl, comm) - - expected_elt_er = { 'TRI_1': np.array([1,1]), - 'TRI_2': np.array([2,4]), - } - expected_elt_dn = { 'TRI_1': np.array([[0,0,1],[0,1,1]][rank]), - 'TRI_2': np.array([[0,2,3],[2,3,3]][rank]), - } - for elt_name, elt_er in expected_elt_er.items(): - elt_n = PT.get_child_from_name(dist_zone, elt_name) - assert np.array_equal(PT.Element.Range(elt_n), elt_er) - assert np.array_equal(PT.maia.getDistribution(elt_n, 'Element')[1], expected_elt_dn[elt_name]) - - expected_bc_pl = np.array([[[1]],[[4]]][rank]) - expected_bc_dn = np.array([[0,1,2],[1,2,2]][rank]) - bc_n = PT.get_node_from_label(dist_zone, 'BC_t') - assert np.array_equal(PT.Subset.getPatch(bc_n)[1], expected_bc_pl) - assert np.array_equal(PT.maia.getDistribution(bc_n, 'Index')[1], expected_bc_dn) - - expected_cell_dn = np.array([[0,1,1],[1,1,1]][rank]) - assert np.array_equal(PT.maia.getDistribution(dist_zone, 'Cell')[1], expected_cell_dn) diff --git a/maia/algo/dist/test/test_std_elements_to_mixed.py b/maia/algo/dist/test/test_std_elements_to_mixed.py deleted file mode 100644 index 2198bf1f..00000000 --- a/maia/algo/dist/test/test_std_elements_to_mixed.py +++ /dev/null @@ -1,109 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np -import os - -import maia.pytree as PT - -from maia.io import file_to_dist_tree -from maia.utils import test_utils as TU - -from maia.algo.dist import convert_elements_to_mixed - -@pytest_parallel.mark.parallel([1,2,3]) -def test_convert_mixed_to_elements(comm): - rank = comm.Get_rank() - size = comm.Get_size() - - yaml_path = os.path.join(TU.mesh_dir, 'hex_prism_pyra_tet.yaml') - dist_tree = file_to_dist_tree(yaml_path, comm) - - ref_dist_tree = PT.deep_copy(dist_tree) - - convert_elements_to_mixed(dist_tree, comm) - - ref_base = PT.get_child_from_label(ref_dist_tree,'CGNSBase_t') - base = PT.get_child_from_label(dist_tree,'CGNSBase_t') - assert PT.get_name(base) == PT.get_name(ref_base) - assert np.all(PT.get_value(base) == PT.get_value(ref_base)) - - ref_zone = PT.get_child_from_label(ref_base, 'Zone_t') - zone = PT.get_child_from_label(base, 'Zone_t') - assert PT.get_name(zone) == PT.get_name(ref_zone) - assert np.all(PT.get_value(zone) == PT.get_value(ref_zone)) - - mixed_nodes = PT.get_children_from_label(zone, 'Elements_t') - assert len(mixed_nodes) == 1 - - mixed_node = mixed_nodes[0] - assert PT.get_value(mixed_node)[0] == 20 - - mixed_er_node = PT.get_child_from_name(mixed_node, 'ElementRange') - assert np.all(PT.get_value(mixed_er_node) == [1,16]) - - mixed_eso_node = PT.get_child_from_name(mixed_node, 'ElementStartOffset') - mixed_ec_node = PT.get_child_from_name(mixed_node, 'ElementConnectivity') - assert mixed_eso_node is not None - assert mixed_ec_node is not None - - if size == 1: - expected_mixed_eso = [ 0, 5, 10, 15, 20, 25, 30, 34, 38, 42, 46, 50, 54, 63, 70, 76, 81] - expected_mixed_ec = [ 7, 1, 6, 9, 4, 7, 3, 5, 10, 8, 7, 1, 2, 7, 6, 7, 2, - 3, 8, 7, 7, 4, 9, 10, 5, 7, 1, 4, 5, 2, 5, 6, 11, 9, - 5, 8, 10, 11, 5, 6, 7, 11, 5, 7, 8, 11, 5, 2, 5, 3, 5, - 9, 11, 10, 17, 1, 2, 5, 4, 6, 7, 10, 9, 14, 2, 3, 5, 7, - 8, 10, 12, 6, 7, 10, 9, 11, 10, 7, 8, 10, 11] - elif size == 2: - if rank == 0: - expected_mixed_eso = [ 0, 5, 10, 15, 20, 25, 30, 34, 38] - expected_mixed_ec = [ 7, 1, 6, 9, 4, 7, 3, 5, 10, 8, 7, 1, 2, 7, 6, 7, 2, - 3, 8, 7, 7, 4, 9, 10, 5, 7, 1, 4, 5, 2, 5, 6, 11, 9, - 5, 8, 10, 11] - elif rank ==1: - expected_mixed_eso = [38, 42, 46, 50, 54, 63, 70, 76, 81] - expected_mixed_ec = [ 5, 6, 7, 11, 5, 7, 8, 11, 5, 2, 5, 3, 5, 9, 11, 10, 17, - 1, 2, 5, 4, 6, 7, 10, 9, 14, 2, 3, 5, 7, 8, 10, 12, 6, - 7, 10, 9, 11, 10, 7, 8, 10, 11] - elif size == 3: - if rank == 0: - expected_mixed_eso = [ 0, 5, 10, 15, 20, 25, 30] - expected_mixed_ec = [ 7, 1, 6, 9, 4, 7, 3, 5, 10, 8, 7, 1, 2, 7, 6, 7, 2, - 3, 8, 7, 7, 4, 9, 10, 5, 7, 1, 4, 5, 2] - elif rank ==1: - expected_mixed_eso = [30, 34, 38, 42, 46, 50] - expected_mixed_ec = [ 5, 6, 11, 9, 5, 8, 10, 11, 5, 6, 7, 11, 5, 7, 8, 11, 5, - 2, 5, 3] - elif rank ==2: - expected_mixed_eso = [50, 54, 63, 70, 76, 81] - expected_mixed_ec = [ 5, 9, 11, 10, 17, 1, 2, 5, 4, 6, 7, 10, 9, 14, 2, 3, 5, - 7, 8, 10, 12, 6, 7, 10, 9, 11, 10, 7, 8, 10, 11] - elif size > 16: - expected_mixed_eso_full = [ 0, 5, 10, 15, 20, 25, 30, 34, 38, 42, 46, 50, 54, 63, 70, 76, 81] - expected_mixed_ec_full = [ 7, 1, 6, 9, 4, 7, 3, 5, 10, 8, 7, 1, 2, 7, 6, 7, 2, - 3, 8, 7, 7, 4, 9, 10, 5, 7, 1, 4, 5, 2, 5, 6, 11, 9, - 5, 8, 10, 11, 5, 6, 7, 11, 5, 7, 8, 11, 5, 2, 5, 3, 5, - 9, 11, 10, 17, 1, 2, 5, 4, 6, 7, 10, 9, 14, 2, 3, 5, 7, - 8, 10, 12, 6, 7, 10, 9, 11, 10, 7, 8, 10, 11] - if rank < 16: - expected_mixed_eso = expected_mixed_eso_full[rank:rank+2] - expected_mixed_ec = expected_mixed_ec_full[expected_mixed_eso[0]:expected_mixed_eso[1]] - else: - expected_mixed_eso = [81] - expected_mixed_ec = [] - - assert np.all(PT.get_value(mixed_eso_node) == expected_mixed_eso) - assert np.all(PT.get_value(mixed_ec_node) == expected_mixed_ec) - - for ref_bc in PT.get_children_from_label(ref_zone, 'BC_t'): - bc = PT.get_child_from_name(zone, PT.get_name(ref_bc)) - ref_pointlist = PT.get_value(PT.get_child_from_name(ref_bc,'PointList')) - pointlist = PT.get_value(PT.get_child_from_name(bc,'PointList')) - assert np.all(pointlist == ref_pointlist) - - ref_fs = PT.get_child_from_label(ref_zone, 'FlowSolution_t') - fs = PT.get_child_from_label(zone, 'FlowSolution_t') - ref_data = PT.get_child_from_label(ref_fs, 'DataArray_t') - data = PT.get_child_from_label(fs, 'DataArray_t') - assert PT.get_name(data) == PT.get_name(ref_data) - assert np.all(PT.get_value(data) == PT.get_value(ref_data)) - diff --git a/maia/algo/dist/test/test_subset_tools.py b/maia/algo/dist/test/test_subset_tools.py deleted file mode 100644 index 07d468d7..00000000 --- a/maia/algo/dist/test/test_subset_tools.py +++ /dev/null @@ -1,55 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype - -from maia.factory.dcube_generator import dcube_generate, dcube_nodal_generate -from maia.algo.dist import subset_tools - -@pytest_parallel.mark.parallel(2) -def test_vtx_ids_to_face_ids_ngon(comm): - tree = dcube_generate(3, 1., [0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - ngon = PT.Zone.NGonNode(zone) - if comm.Get_rank() == 0: - vtx_ids = np.array([4,5,17,18], pdm_dtype) - expected_face_ids = np.array([3], pdm_dtype) - elif comm.Get_rank() == 1: - vtx_ids = np.array([5,7,27,25,26,8], pdm_dtype) - expected_face_ids = np.array([36], pdm_dtype) - face_ids = subset_tools.vtx_ids_to_face_ids(vtx_ids, ngon, comm, True) - assert (face_ids == expected_face_ids).all() - -@pytest_parallel.mark.parallel(2) -def test_vtx_ids_to_face_ids_elmt(comm): - tree = dcube_nodal_generate(3, 1., [0,0,0], 'HEXA_8', comm) - zone = PT.get_all_Zone_t(tree)[0] - quad = PT.get_node_from_predicate(zone, lambda n: PT.get_label(n)=='Elements_t' and PT.Element.CGNSName(n)=='QUAD_4') - if comm.Get_rank() == 0: - vtx_ids = np.array([2,4,1,5 , 13,16,22,25], pdm_dtype) - expected_face_ids = np.array([1, 2, 12], pdm_dtype) - # NB : more than 2 faces expected because we combine vtx_ids from both ranks - elif comm.Get_rank() == 1: - vtx_ids = np.array([3,6,15,12, 17,26,27,18], pdm_dtype) - expected_face_ids = np.array([13,22,24], pdm_dtype) - face_ids = subset_tools.vtx_ids_to_face_ids(vtx_ids, quad, comm, True) - assert (face_ids == expected_face_ids).all() - -@pytest_parallel.mark.parallel([1,2]) -def test_convert_subset_as_facelist(comm): - tree = maia.factory.generate_dist_block(3, 'S', comm) - maia.algo.dist.convert_s_to_u(tree, 'NGON', comm) - - subset_tools.convert_subset_as_facelist(tree, 'Base/zone/ZoneBC/Xmax', comm) - - xmax = PT.get_node_from_name(tree, 'Xmax') - distri = MT.getDistribution(xmax, 'Index')[1] - pl = PT.get_child_from_name(xmax, 'PointList')[1] - assert PT.Subset.GridLocation(xmax) == 'FaceCenter' - assert pl.ndim == 2 - assert (pl[0] == [3,6,9,12][distri[0]:distri[1]]).all() diff --git a/maia/algo/dist/test/test_vertex_list.py b/maia/algo/dist/test/test_vertex_list.py deleted file mode 100644 index da2e195e..00000000 --- a/maia/algo/dist/test/test_vertex_list.py +++ /dev/null @@ -1,410 +0,0 @@ -import numpy as np -import pytest -import pytest_parallel - -import maia -import maia.pytree as PT -import maia.pytree.maia as MT -from maia.pytree.yaml import parse_yaml_cgns - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.factory import dcube_generator -from maia.algo.dist import vertex_list as VL -from maia.utils import par_utils - -# TODO replace dcube_generate by handwritten mesh (4_cubes?) - -@pytest_parallel.mark.parallel([1,3]) -def test_face_ids_to_vtx_ids(comm): - tree = dcube_generator.dcube_generate(3,1.,[0,0,0], comm) - ngon = PT.get_node_from_name(tree, "NGonElements") - - offset, face_vtx = VL.face_ids_to_vtx_ids(np.array([3,6,2]), ngon, comm) - assert (offset == np.arange(0,(3+1)*4,4)).all() - assert (face_vtx == [5,8,7,4, 11,14,15,12, 3,6,5,2]).all() - - offset, face_vtx_d = VL.face_ids_to_vtx_ids(np.array([1,4,5]), ngon, comm) - assert (offset == np.arange(0,(3+1)*4,4)).all() - assert (face_vtx_d == [2,5,4,1, 6,9,8,5, 10,13,14,11]).all() - -@pytest_parallel.mark.parallel(2) -def test_filter_vtx_coordinates(comm): - empty = np.empty(0, int) - tree = dcube_generator.dcube_generate(5,1.,[0,0,0], comm) - vtx_coords = PT.get_node_from_label(tree, 'GridCoordinates_t') - vtx_distri = PT.get_value(MT.getDistribution(PT.get_all_Zone_t(tree)[0], 'Vertex')) - if comm.Get_rank() == 1: - requested_vtx_ids = np.array([2,6,7,106,3,103,107,102]) - expected_vtx_coords = np.array([[0.25, 0., 0.], [0., 0.25, 0.], [0.25, 0.25, 0.], [0., 0.25, 1.], - [0.5, 0., 0.], [0.5, 0., 1.], [0.25, 0.25, 1.], [0.25, 0., 1.]]) - else: - requested_vtx_ids = empty - expected_vtx_coords = np.empty((0,3), dtype=np.float64) - - received_coords = VL.filter_vtx_coordinates(vtx_coords, vtx_distri, requested_vtx_ids, comm) - assert (received_coords == expected_vtx_coords).all() - -@pytest_parallel.mark.parallel(2) -def test_get_extended_pl(comm): - tree = dcube_generator.dcube_generate(3,1.,[0,0,0], comm) - ngon = PT.get_node_from_name(tree, "NGonElements") - if comm.Get_rank() == 0: - pl = np.array([1,2,3]) - pl_d = np.array([9,10,11]) - pl_idx = np.array([0,4,8,12]) - pl_vtx = np.array([2,5,4,1, 3,6,5,2, 5,8,7,4]) - skip_f = np.array([True, True, True]) - else: - pl = np.array([4]) - pl_d = np.array([12]) - pl_idx = np.array([0,4]) - pl_vtx = np.array([6,9,8,5]) - skip_f = np.array([False]) - ext_pl, ext_pl_d = VL.get_extended_pl(pl, pl_d, pl_idx, pl_vtx, comm) - assert (ext_pl == [1,2,3,4]).all() - assert (ext_pl_d == [9,10,11,12]).all() - - ext_pl, ext_pl_d = VL.get_extended_pl(pl, pl_d, pl_idx, pl_vtx, comm, skip_f) - if comm.Get_rank() == 0: - assert len(ext_pl) == len(ext_pl_d) == 0 - else: - assert (ext_pl == [1,2,3,4]).all() - assert (ext_pl_d == [9,10,11,12]).all() - -def test_search_by_intersection(): - empty = np.empty(0, int) - plv, plv_opp, face_is_treated = VL._search_by_intersection(np.array([0]), empty, empty) - assert (plv == empty).all() - assert (plv_opp == empty).all() - assert (face_is_treated == empty).all() - - #Some examples from cube3, top/down jns - #Single face can not be treated - plv, plv_opp, face_is_treated = \ - VL._search_by_intersection( \ - [0 ,4], \ - [ 5, 8, 7, 4], \ - [22,25,26,23] \ - ) - assert (plv == [4,5,7,8]).all() - assert (plv_opp == [0,0,0,0]).all() - assert (face_is_treated == [False]).all() - - #Example from Cube4 : solo face will not be treated - plv, plv_opp, face_is_treated = \ - VL._search_by_intersection( \ - [0 , 4 , 8 , 12], \ - [ 6,10, 9, 5, 12,16,15,11, 3, 7, 6, 2], \ - [37,41,42,38, 43,47,48,44, 34,38,39,35] \ - ) - - assert (plv == [ 2, 3, 5, 6, 7, 9,10,11,12,15,16]).all() - assert (plv_opp == [34,35,37,38,39,41,42, 0, 0, 0, 0]).all() - assert (face_is_treated == [True, False, True]).all() - - #Here is a crazy exemple where third face can not be treated on first pass, - # but is the treated thanks to already treated faces - plv, plv_opp, face_is_treated = \ - VL._search_by_intersection( \ - [0 , 4 , 9 ,14], \ - [ 2, 4, 3, 1, 10, 9, 6, 8, 7, 5, 6, 3, 4, 7], \ - [12,11,13,14, 19,20,17,18,16, 13,16,15,17,14] \ - ) - assert (plv == np.arange(1,10+1)).all() - assert (plv_opp == np.arange(11,20+1)).all() - assert (face_is_treated == [True, True, True]).all() - -@pytest_parallel.mark.parallel(3) -def test_search_with_geometry(comm): - empty = np.empty(0, int) - tree = dcube_generator.dcube_generate(4,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - gc = PT.new_GridConnectivity() - PT.new_GridConnectivityProperty(periodic={'translation' : [0., 0., 1.]},parent=gc) - - if comm.Get_rank() == 0: - pl_face_vtx_idx = np.array([0,4]) - pl_face_vtx = np.array([2,6,7,3]) - pld_face_vtx = np.array([51,55,54,50]) - elif comm.Get_rank() == 2: - pl_face_vtx_idx = np.array([0,4,8]) - pl_face_vtx = np.array([11,15,16,12, 13,14,10,9]) - pld_face_vtx = np.array([64,63,59,60, 61,57,58,62]) - else: - pl_face_vtx_idx = np.zeros(1, np.int32) - pl_face_vtx = empty - pld_face_vtx = empty - - plv, plvd = VL._search_with_geometry(zone, zone, gc, pl_face_vtx_idx, pl_face_vtx, pld_face_vtx, comm) - - if comm.Get_rank() == 0: - assert (plv == [2,3,6,7]).all() - assert (plvd == [50,51,54,55]).all() - elif comm.Get_rank() == 2: - assert (plv == [9,10,11,12,13,14,15,16]).all() - assert (plvd == [57,58,59,60,61,62,63,64]).all() - else: - assert (plv.size == plvd.size == 0) - -class Test_generate_jn_vertex_list(): - @pytest_parallel.mark.parallel([1,2,4]) - def test_single_zone_topo(self, comm): - #With this configuration, we have locally (per rank) isolated faces when np=4 - tree = dcube_generator.dcube_generate(4,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - PT.rm_children_from_label(zone, 'ZoneBC_t') - #Create fake jn - zgc = PT.new_ZoneGridConnectivity(parent=zone) - gcA = PT.new_GridConnectivity('matchA', PT.get_name(zone), 'Abutting1to1', loc='FaceCenter', parent=zgc) - full_pl = np.array([1,2,3,4,5,6,7,8,9]) - full_pl_opp = np.array([28,29,30,31,32,33,34,35,36]) - distri_pl = par_utils.uniform_distribution(9, comm) - PT.new_IndexArray('PointList' , full_pl [distri_pl[0]:distri_pl[1]].reshape(1,-1), gcA) - PT.new_IndexArray('PointListDonor', full_pl_opp[distri_pl[0]:distri_pl[1]].reshape(1,-1), gcA) - MT.newDistribution({'Index' : distri_pl}, gcA) - - gc_path = "Base/zone/ZoneGridConnectivity/matchA" - pl_vtx, pl_vtx_opp, distri_jn_vtx = VL.generate_jn_vertex_list(tree, gc_path, comm) - - expected_full_pl_vtx = np.arange(1,16+1) - expected_full_pl_vtx_opp = np.arange(49,64+1) - assert (distri_jn_vtx == par_utils.uniform_distribution(16, comm)).all() - assert (pl_vtx == expected_full_pl_vtx [distri_jn_vtx[0]:distri_jn_vtx[1]]).all() - assert (pl_vtx_opp == expected_full_pl_vtx_opp[distri_jn_vtx[0]:distri_jn_vtx[1]]).all() - - @pytest_parallel.mark.parallel([2,4]) - def test_multi_zone_topo(self, comm): - tree = dcube_generator.dcube_generate(4,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - zone[0] = 'zoneA' - PT.rm_children_from_label(zone, 'ZoneBC_t') - - #Create other zone - tree2 = dcube_generator.dcube_generate(4,1.,[1,0,0], comm) - zoneB = PT.get_all_Zone_t(tree2)[0] - zoneB[0] = 'zoneB' - PT.rm_children_from_label(zoneB, 'ZoneBC_t') - PT.add_child(PT.get_node_from_name(tree, 'Base'), zoneB) - - #Create fake jn - zgc = PT.new_ZoneGridConnectivity(parent=zone) - gcA = PT.new_GridConnectivity('matchA', 'zoneB', 'Abutting1to1', loc='FaceCenter', parent=zgc) - full_pl = np.array([64,65,66,67,68,69,70,71,72]) #xmax - full_pl_opp = np.array([37,38,39,40,41,42,43,44,45]) #xmin - distri_pl = par_utils.uniform_distribution(9, comm) - PT.new_IndexArray('PointList' , full_pl [distri_pl[0]:distri_pl[1]].reshape(1,-1), gcA) - PT.new_IndexArray('PointListDonor', full_pl_opp[distri_pl[0]:distri_pl[1]].reshape(1,-1), gcA) - MT.newDistribution({'Index' : distri_pl}, gcA) - - gc_path = "Base/zoneA/ZoneGridConnectivity/matchA" - pl_vtx, pl_vtx_opp, distri_jn_vtx = VL.generate_jn_vertex_list(tree, gc_path, comm) - - expected_full_pl_vtx = np.arange(4, 64+1, 4) - expected_full_pl_vtx_opp = np.arange(1, 64+1, 4) - assert (distri_jn_vtx == par_utils.uniform_distribution(16, comm)).all() - assert (pl_vtx == expected_full_pl_vtx [distri_jn_vtx[0]:distri_jn_vtx[1]]).all() - assert (pl_vtx_opp == expected_full_pl_vtx_opp[distri_jn_vtx[0]:distri_jn_vtx[1]]).all() - - @pytest_parallel.mark.parallel(3) - def test_single_zone_all(self, comm): - #Faces that should be treated during phase 1,2 and 3 for each proc : - # P0 : (2,0,1) P1 : (0,1,0) P2 : (0,1,1) - tree = dcube_generator.dcube_generate(5,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - PT.rm_children_from_label(zone, 'ZoneBC_t') - zgc = PT.new_ZoneGridConnectivity(parent=zone) - gcA = PT.new_GridConnectivity('matchA', PT.get_name(zone), 'Abutting1to1', loc='FaceCenter', parent=zgc) - if comm.Get_rank() == 0: - pl_distri = [0,3,6] - expt_jn_distri = [0, 7, 21] - elif comm.Get_rank() == 1: - pl_distri = [3,4,6] - expt_jn_distri = [7, 14, 21] - elif comm.Get_rank() == 2: - pl_distri = [4,6,6] - expt_jn_distri = [14, 21, 21] - - PT.new_IndexArray('PointList' , (np.array([1,2,4,11,13,16]) [pl_distri[0]:pl_distri[1]]).reshape(1,-1), gcA) - PT.new_IndexArray('PointListDonor', (np.array([65,66,68,75,77,80])[pl_distri[0]:pl_distri[1]]).reshape(1,-1), gcA) - MT.newDistribution({'Index' : pl_distri}, gcA) - - gc_path = "Base/zone/ZoneGridConnectivity/matchA" - pl_vtx, pld_vtx, distri_jn_vtx = VL.generate_jn_vertex_list(tree, gc_path, comm) - - expected_full_pl_vtx = np.array([1,2,3,4,5,6,7,8,9,10,13,14,16,17,18,19,20,21,22,24,25]) - expected_full_pld_vtx = expected_full_pl_vtx + 100 - assert (distri_jn_vtx == expt_jn_distri).all() - assert (pl_vtx == expected_full_pl_vtx [distri_jn_vtx[0]:distri_jn_vtx[1]]).all() - assert (pld_vtx == expected_full_pld_vtx[distri_jn_vtx[0]:distri_jn_vtx[1]]).all() - - @pytest_parallel.mark.parallel(2) - def test_multi_zone_geo(self, comm): - tree = dcube_generator.dcube_generate(4,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - zone[0] = 'zoneA' - PT.rm_children_from_label(zone, 'ZoneBC_t') - - #Create other zone - tree2 = dcube_generator.dcube_generate(4,1.,[1,0,0], comm) - zoneB = PT.get_all_Zone_t(tree2)[0] - zoneB[0] = 'zoneB' - PT.rm_children_from_label(zoneB, 'ZoneBC_t') - PT.add_child(PT.get_node_from_name(tree, 'Base'), zoneB) - - #Create fake jn such that we have only isolated faces - zgc = PT.new_ZoneGridConnectivity(parent=zone) - gcA = PT.new_GridConnectivity('matchA', 'zoneB', 'Abutting1to1', loc='FaceCenter', parent=zgc) - full_pl = np.array([64,66,71]) #xmax - full_pl_opp = np.array([37,39,44]) #xmin - distri_pl = par_utils.uniform_distribution(3, comm) - PT.new_IndexArray('PointList' , full_pl [distri_pl[0]:distri_pl[1]].reshape(1,-1), gcA) - PT.new_IndexArray('PointListDonor', full_pl_opp[distri_pl[0]:distri_pl[1]].reshape(1,-1), gcA) - MT.newDistribution({'Index' : distri_pl}, gcA) - - gc_path = "Base/zoneA/ZoneGridConnectivity/matchA" - pl_vtx, pl_vtx_opp, distri_jn_vtx = VL.generate_jn_vertex_list(tree, gc_path, comm) - - expected_dist = [0,6,12] if comm.Get_rank() == 0 else [6,12,12] - expected_full_pl_vtx = [4,8,12,16,20,24,28,32,40,44,56,60] - expected_full_pl_vtx_opp = [1,5,9,13,17,21,25,29,37,41,53,57] - assert (distri_jn_vtx == expected_dist).all() - assert (pl_vtx == expected_full_pl_vtx [distri_jn_vtx[0]:distri_jn_vtx[1]]).all() - assert (pl_vtx_opp == expected_full_pl_vtx_opp[distri_jn_vtx[0]:distri_jn_vtx[1]]).all() - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("have_isolated_faces", [False, True]) -def test_generate_jns_vertex_list(comm, have_isolated_faces): - #For this test, we reuse previous test case, but with 2 jns, - # and do not assert on values - tree = dcube_generator.dcube_generate(4,1.,[0,0,0], comm) - zoneA = PT.get_all_Zone_t(tree)[0] - zoneA[0] = 'zoneA' - PT.rm_children_from_label(zoneA, 'ZoneBC_t') - - #Create other zone - tree2 = dcube_generator.dcube_generate(4,1.,[1,0,0], comm) - zoneB = PT.get_all_Zone_t(tree2)[0] - zoneB[0] = 'zoneB' - PT.rm_children_from_label(zoneB, 'ZoneBC_t') - PT.add_child(PT.get_node_from_name(tree, 'Base'), zoneB) - - #Create fake jns - zgc = PT.new_ZoneGridConnectivity(parent=zoneA) - gcA = PT.new_GridConnectivity('matchA', 'zoneB', 'Abutting1to1', loc='FaceCenter', parent=zgc) - full_pl = np.array([64,65,66,67,68,69,70,71,72], pdm_dtype) #xmax - full_pl_opp = np.array([37,38,39,40,41,42,43,44,45], pdm_dtype) #xmin - distri_pl = par_utils.uniform_distribution(9, comm) - PT.new_IndexArray('PointList' , full_pl [distri_pl[0]:distri_pl[1]].reshape(1,-1), gcA) - PT.new_IndexArray('PointListDonor', full_pl_opp[distri_pl[0]:distri_pl[1]].reshape(1,-1), gcA) - MT.newDistribution({'Index' : distri_pl}, gcA) - - zgc = PT.new_ZoneGridConnectivity(parent=zoneB) - gcB = PT.new_GridConnectivity('matchB', 'Base/zoneA', 'Abutting1to1', loc='FaceCenter', parent=zgc) - full_pl = np.array([64,65,66,67,68,69,70,71,72], pdm_dtype) #xmax - full_pl_opp = np.array([37,38,39,40,41,42,43,44,45], pdm_dtype) #xmin - distri_pl = par_utils.uniform_distribution(9, comm) - PT.new_IndexArray('PointListDonor', full_pl [distri_pl[0]:distri_pl[1]].reshape(1,-1), gcB) - PT.new_IndexArray('PointList' , full_pl_opp[distri_pl[0]:distri_pl[1]].reshape(1,-1), gcB) - MT.newDistribution({'Index' : distri_pl}, gcB) - - VL.generate_jns_vertex_list(tree, comm, have_isolated_faces=have_isolated_faces) - - assert len(PT.get_nodes_from_name(tree, "ZoneGridConnectivity")) == 2 - assert PT.get_node_from_name(tree, "matchA#Vtx") is not None - jn_vtx = PT.get_node_from_name(tree, "matchB#Vtx") - assert jn_vtx is not None and PT.Subset.GridLocation(jn_vtx) == 'Vertex' - assert PT.get_label(jn_vtx) == 'GridConnectivity_t' and PT.get_value(jn_vtx) == PT.get_value(gcB) - - -@pytest_parallel.mark.parallel(1) -def test_perio_and_match_case(comm): - # A user reported case with a translation periodic (1 cell deepth) + a match - # who caused troubles - yt = """ -CGNSLibraryVersion CGNSLibraryVersion_t 3.1: -Base CGNSBase_t I4 [3, 3]: - zoneA Zone_t I4 [[3, 2, 0], [3, 2, 0], [2, 1, 0]]: - ZoneType ZoneType_t 'Structured': - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t: - R8 : [[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0]], [[2.0, 2.0], [2.0, 2.0], [2.0, 2.0]]] - CoordinateY DataArray_t: - R8 : [[[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]], [[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]], [[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]]] - CoordinateZ DataArray_t: - R8 : [[[0.0, 0.25], [0.0, 0.25], [0.0, 0.25]], [[0.0, 0.25], [0.0, 0.25], [0.0, 0.25]], [[0.0, 0.25], [0.0, 0.25], [0.0, 0.25]]] - ZoneGridConnectivity ZoneGridConnectivity_t: - match1 GridConnectivity1to1_t 'zoneB': - PointRange IndexRange_t I4 [[3, 3], [1, 3], [1, 2]]: - PointRangeDonor IndexRange_t I4 [[1, 1], [1, 3], [1, 2]]: - Transform "int[IndexDimension]" I4 [1, 2, 3]: - PerioA GridConnectivity1to1_t 'zoneA': - PointRange IndexRange_t I4 [[1, 3], [1, 3], [1, 1]]: - PointRangeDonor IndexRange_t I4 [[1, 3], [1, 3], [2, 2]]: - GridConnectivityProperty GridConnectivityProperty_t: - Periodic Periodic_t: - RotationAngle DataArray_t R4 [0.0, 0.0, 0.0]: - RotationCenter DataArray_t R4 [0.0, 0.0, 0.0]: - Translation DataArray_t R4 [0.0, 0.0, 0.25]: - Transform "int[IndexDimension]" I4 [1, 2, 3]: - PerioB GridConnectivity1to1_t 'zoneA': - PointRange IndexRange_t I4 [[1, 3], [1, 3], [2, 2]]: - PointRangeDonor IndexRange_t I4 [[1, 3], [1, 3], [1, 1]]: - Transform "int[IndexDimension]" I4 [1, 2, 3]: - GridConnectivityProperty GridConnectivityProperty_t: - Periodic Periodic_t: - RotationAngle DataArray_t R4 [0.0, 0.0, 0.0]: - RotationCenter DataArray_t R4 [0.0, 0.0, 0.0]: - Translation DataArray_t R4 [0.0, 0.0, -0.25]: - zoneB Zone_t I4 [[3, 2, 0], [4, 3, 0], [2, 1, 0]]: - ZoneType ZoneType_t 'Structured': - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t: - R8 : [[[2.0, 2.0], [2.0, 2.0], [2.0, 2.0], [2.0, 2.0]], [[3.0, 3.0], [3.0, 3.0], [3.0, 3.0], [3.0, 3.0]], [[4.0, - 4.0], [4.0, 4.0], [4.0, 4.0], [4.0, 4.0]]] - CoordinateY DataArray_t: - R8 : [[[0.0, 0.0], [1.0, 1.0], [2.0, 2.0], [3.0, 3.0]], [[0.0, 0.0], [1.0, 1.0], [2.0, 2.0], [3.0, 3.0]], [[0.0, - 0.0], [1.0, 1.0], [2.0, 2.0], [3.0, 3.0]]] - CoordinateZ DataArray_t: - R8 : [[[0.0, 0.25], [0.0, 0.25], [0.0, 0.25], [0.0, 0.25]], [[0.0, 0.25], [0.0, 0.25], [0.0, 0.25], [0.0, 0.25]], - [[0.0, 0.25], [0.0, 0.25], [0.0, 0.25], [0.0, 0.25]]] - ZoneGridConnectivity ZoneGridConnectivity_t: - match2 GridConnectivity1to1_t 'zoneA': - PointRange IndexRange_t I4 [[1, 1], [1, 3], [1, 2]]: - PointRangeDonor IndexRange_t I4 [[3, 3], [1, 3], [1, 2]]: - Transform "int[IndexDimension]" I4 [1, 2, 3]: - PerioA GridConnectivity1to1_t 'zoneB': - PointRange IndexRange_t I4 [[1, 3], [1, 4], [1, 1]]: - PointRangeDonor IndexRange_t I4 [[1, 3], [1, 4], [2, 2]]: - Transform "int[IndexDimension]" I4 [1, 2, 3]: - GridConnectivityProperty GridConnectivityProperty_t: - Periodic Periodic_t: - RotationAngle DataArray_t R4 [0.0, 0.0, 0.0]: - RotationCenter DataArray_t R4 [0.0, 0.0, 0.0]: - Translation DataArray_t R4 [0.0, 0.0, 0.25]: - PerioB GridConnectivity1to1_t 'zoneB': - PointRange IndexRange_t I4 [[1, 3], [1, 4], [2, 2]]: - PointRangeDonor IndexRange_t I4 [[1, 3], [1, 4], [1, 1]]: - Transform "int[IndexDimension]" I4 [1, 2, 3]: - GridConnectivityProperty GridConnectivityProperty_t: - Periodic Periodic_t: - RotationAngle DataArray_t R4 [0.0, 0.0, 0.0]: - RotationCenter DataArray_t R4 [0.0, 0.0, 0.0]: - Translation DataArray_t R4 [0.0, 0.0, -0.25]: - """ - tree = parse_yaml_cgns.to_cgns_tree(yt) - tree = maia.factory.full_to_dist_tree(tree, comm) - maia.algo.dist.convert_s_to_ngon(tree, comm) - VL.generate_jns_vertex_list(tree, comm) - - zoneA, zoneB = PT.get_all_Zone_t(tree) - node = PT.get_node_from_name(zoneA, 'match1#Vtx') - assert np.array_equal(PT.get_node_from_name(node, 'PointList' )[1], [[3,6,9,12,15,18]]) - assert np.array_equal(PT.get_node_from_name(node, 'PointListDonor')[1], [[1,4,7,13,16,19]]) - node = PT.get_node_from_name(zoneB, 'match2#Vtx') - assert np.array_equal(PT.get_node_from_name(node, 'PointListDonor')[1], [[3,6,9,12,15,18]]) - assert np.array_equal(PT.get_node_from_name(node, 'PointList' )[1], [[1,4,7,13,16,19]]) - node = PT.get_node_from_name(zoneA, 'PerioA#Vtx') - assert np.array_equal(PT.get_node_from_name(node, 'PointList')[1], [[1,2,3,4,5,6,7,8,9]]) - node = PT.get_node_from_name(zoneA, 'PerioB#Vtx') - assert np.array_equal(PT.get_node_from_name(node, 'PointList')[1], [[10,11,12,13,14,15,16,17,18]]) - diff --git a/maia/algo/dist/transform.py b/maia/algo/dist/transform.py deleted file mode 100644 index 6c5fa540..00000000 --- a/maia/algo/dist/transform.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np - -import maia.pytree.maia as MT - -from maia.algo import transform -from maia.transfer import protocols as EP - -def transform_affine_zone(zone, - vtx_ids, - comm, - rotation_center=np.zeros(3), - rotation_angle=np.zeros(3), - translation=np.zeros(3), - apply_to_fields=False): - - distri_vtx = MT.getDistribution(zone, 'Vertex')[1] - - all_vtx = np.arange(distri_vtx[0]+1, distri_vtx[1]+1, dtype=distri_vtx.dtype) - PTP = EP.PartToPart([vtx_ids], [all_vtx], comm) - - vtx_mask = np.zeros(all_vtx.size, dtype=bool) - vtx_mask[PTP.get_referenced_lnum2()[0]-1] = True - - transform.transform_affine_zone(zone, - vtx_mask, - rotation_center, - rotation_angle, - translation, - apply_to_fields) \ No newline at end of file diff --git a/maia/algo/dist/vertex_list.py b/maia/algo/dist/vertex_list.py deleted file mode 100644 index 88ea99b0..00000000 --- a/maia/algo/dist/vertex_list.py +++ /dev/null @@ -1,559 +0,0 @@ -import mpi4py.MPI as MPI -import numpy as np -import itertools - -import Pypdm.Pypdm as PDM -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.utils import py_utils, np_utils, par_utils, as_pdm_gnum - -from maia.algo.dist import matching_jns_tools as MJT - -from maia.transfer import protocols as EP - -def shifted_eso(ngon): - eso = PT.get_node_from_path(ngon, 'ElementStartOffset')[1] - return (eso - eso[0]).astype(np.int32, copy=False) - -def face_ids_to_vtx_ids(face_ids, ngon, comm): - """ - From an array of face ids, search in the distributed NGon node - the id of vertices belonging to the faces. - - Return a tuple (vtx_offset, vtx_list). - The offset array indicates to which face the vertices belong. - Note that vertex ids can appear twice (or more) in vtx_list if they are shared by multiple faces - """ - distri_ngon = PT.get_value(MT.getDistribution(ngon, 'Element')) - - dist_data = PT.get_child_from_name(ngon, 'ElementConnectivity')[1] - b_stride = np.diff(PT.get_child_from_name(ngon, 'ElementStartOffset')[1]) - b_stride = np_utils.safe_int_cast(b_stride, np.int32) - - # Get the vertex associated to the faces in FaceList - p_stride, part_data = EP.block_to_part_strided(b_stride, dist_data, \ - distri_ngon, [face_ids], comm) - - face_offset_l = np_utils.sizes_to_indices(p_stride[0]) - - return face_offset_l, part_data[0] - -def filter_vtx_coordinates(grid_coords_node, distri_vtx, requested_vtx_ids, comm): - """ - Get the coordinates of requested vertices ids (wraps BlockToPart) and - return it as a numpy (n,3) array - """ - dist_data = dict() - for data in PT.iter_children_from_label(grid_coords_node, 'DataArray_t'): - dist_data[PT.get_name(data)] = data[1] - - part_data = EP.block_to_part(dist_data, distri_vtx, [requested_vtx_ids], comm) - - cx, cy, cz = part_data['CoordinateX'][0], part_data['CoordinateY'][0], part_data['CoordinateZ'][0] - - return np.array([cx,cy,cz], order='F').transpose() - -def get_extended_pl(pl, pl_d, face_vtx_idx_pl, face_vtx_pl, comm, faces_to_skip=None): - """ - Extend a local list of face ids (and its donor) by adding face ids of other procs - that are connected to the faces by at least one vertex. - - faces_to_skip is either None, or a boolean array of size len(pl). - If faces_to_skip[i]==True, it means the face is not considered - """ - - pl_vtx, pl_vtx_face_idx, pl_vtx_face = np_utils.reverse_connectivity(pl , face_vtx_idx_pl, face_vtx_pl) - _ , _ , pl_vtx_face_d = np_utils.reverse_connectivity(pl_d, face_vtx_idx_pl, face_vtx_pl) - if faces_to_skip is not None: - idx_to_extract = np_utils.arange_with_jumps(face_vtx_idx_pl,faces_to_skip) - restricted_pl_vtx = np.unique(face_vtx_pl[idx_to_extract]) - else: - restricted_pl_vtx = pl_vtx - - # Exchange to locally have the list of *all* jn faces related to vertex - p_stride = [np.diff(pl_vtx_face_idx).astype(np.int32, copy=False)] - - part_data = {'vtx_to_face' : [pl_vtx_face], - 'vtx_to_face_d' : [pl_vtx_face_d]} - - PTB = EP.PartToBlock(None, [pl_vtx], comm, keep_multiple=True) - dist_data = dict() - for field_name, p_field in part_data.items(): - d_stride, d_field = PTB.exchange_field(p_field, p_stride) - dist_data[field_name] = d_field - - - #Recreate stride for all vertex - first, count, total = PTB.getBeginNbEntryAndGlob() - b_stride = np.zeros(count, np.int32) - b_stride[PTB.getBlockGnumCopy() - first - 1] = d_stride - - p_stride, part_data = EP.block_to_part_strided(b_stride, dist_data, \ - PTB.getDistributionCopy(), [restricted_pl_vtx], comm) - - extended_pl, unique_idx = np.unique(part_data["vtx_to_face"][0], return_index=True) - extended_pl_d = part_data["vtx_to_face_d"][0][unique_idx] - - return extended_pl, extended_pl_d - -def _search_by_intersection(pl_face_vtx_idx, pl_face_vtx, pld_face_vtx): - """ - Take two face_vtx arrays of matching faces and try to construct the matching - vertices list using face intersections (topologic). - Return two array of vertices : first one is simply unique(pl_face_vtx), second - one is same size and stores matching vertices *or* 0 if not found. - Also return an bool array of size n_face indicating if face has been treated - (ie all of its vertices have been determined) or not. - - Intersection method assumes that 2 matching faces have inverted - face_vtx ordering, but does not necessarily start with same vtx, - ie A B C D -> C' B' A' D'. The idea of the method is to find some groups - of faces and maching faces sharing a unique sequence of vertices, - in order to identificate the common starting point. - """ - - n_face = len(pl_face_vtx_idx) - 1 - pl_vtx_local = np.unique(pl_face_vtx) - pl_vtx_local_opp = np.zeros_like(pl_vtx_local) - face_is_treated = np.zeros(n_face, dtype=bool) - - # Build connectivity vtx -> list of faces to which the vtx belongs - r_pl, vtx_face_idx, vtx_face = np_utils.reverse_connectivity(np.arange(n_face), pl_face_vtx_idx, pl_face_vtx) - - #Invert dictionnary to have couple of faces -> list of shared vertices - interfaces_to_nodes = dict() - for ivtx, vtx in enumerate(r_pl): - faces = vtx_face[vtx_face_idx[ivtx]: vtx_face_idx[ivtx+1]] - for pair in itertools.combinations(sorted(faces), 2): - try: - interfaces_to_nodes[pair].append(vtx) - except KeyError: - interfaces_to_nodes[pair] = [vtx] - - vtx_g_to_l = {v:i for i,v in enumerate(pl_vtx_local)} - - # TODO re-write this algorithm - # too long - # for + face_is_treated.all() : it is at least quadratic => replace face_is_treated by stack - # Non-numpy Python is slow - for interface, vtx in interfaces_to_nodes.items(): - # For each couple of faces, we have a list of shared vertices : (fA,fB) -> [vtx0, .. vtxN] - fA_idx = slice(pl_face_vtx_idx[interface[0]],pl_face_vtx_idx[interface[0]+1]) - fB_idx = slice(pl_face_vtx_idx[interface[1]],pl_face_vtx_idx[interface[1]+1]) - - #Build the list of shared vertices for the two *opposite* faces - opp_face_vtx_a = pld_face_vtx[fA_idx] - opp_face_vtx_b = pld_face_vtx[fB_idx] - opp_vtx = np.intersect1d(opp_face_vtx_a, opp_face_vtx_b) - - # If vertices are following, we can retrieve order. We may loop in normal - # or reverse order depending on which vtx appears first - subset_vtx = py_utils.get_ordered_subset(vtx, pl_face_vtx[fA_idx]) - if subset_vtx is not None: - subset_vtx_opp = py_utils.get_ordered_subset(opp_vtx, opp_face_vtx_a) - else: - subset_vtx = py_utils.get_ordered_subset(vtx, pl_face_vtx[fB_idx]) - if subset_vtx is not None: - subset_vtx_opp = py_utils.get_ordered_subset(opp_vtx, opp_face_vtx_b) - - # Skip non continous vertices and treat faces if possible - if subset_vtx is not None: - l_vertices = [vtx_g_to_l[v] for v in subset_vtx] - assert subset_vtx_opp is not None - assert len(opp_vtx) == len(l_vertices) - pl_vtx_local_opp[l_vertices] = subset_vtx_opp[::-1] - - for face in interface: - if not face_is_treated[face]: - face_vtx = pl_face_vtx[pl_face_vtx_idx[face]:pl_face_vtx_idx[face+1]] - opp_face_vtx = pld_face_vtx[pl_face_vtx_idx[face]:pl_face_vtx_idx[face+1]] - - ordered_vtx = np_utils.roll_from(face_vtx, start_value = subset_vtx[0]) - ordered_vtx_opp = np_utils.roll_from(opp_face_vtx, start_value = subset_vtx_opp[-1], reverse=True) - - pl_vtx_local_opp[[vtx_g_to_l[k] for k in ordered_vtx]] = ordered_vtx_opp - - face_is_treated[list(interface)] = True - - #We may do the where first to extract the untreated tests, and remove the treated faces - # at each iteration. Stop when array is empty - someone_changed = True - while (not face_is_treated.all() and someone_changed): - someone_changed = False - for face in np.where(~face_is_treated)[0]: - face_vtx = pl_face_vtx [pl_face_vtx_idx[face]:pl_face_vtx_idx[face+1]] - l_vertices = [vtx_g_to_l[v] for v in face_vtx] - for i, vtx_opp in enumerate(pl_vtx_local_opp[l_vertices]): - #Get any already deduced opposed vertex - if vtx_opp != 0: - opp_face_vtx = pld_face_vtx[pl_face_vtx_idx[face]:pl_face_vtx_idx[face+1]] - ordered_vtx = np_utils.roll_from(face_vtx, start_value = pl_vtx_local[l_vertices[i]]) - ordered_vtx_opp = np_utils.roll_from(opp_face_vtx, start_value = vtx_opp, reverse=True) - - pl_vtx_local_opp[[vtx_g_to_l[k] for k in ordered_vtx]] = ordered_vtx_opp - face_is_treated[face] = True - someone_changed = True - break - - return pl_vtx_local, pl_vtx_local_opp, face_is_treated - -def _search_with_geometry(zone, zone_d, jn, pl_face_vtx_idx, pl_face_vtx, pld_face_vtx, comm): - """ - Take two face_vtx arrays describing one or more matching faces (face link is given - by pl_face_vtx_idx array), and try to construct the matching - vertices list using face intersections (geometric). - Return two array of vertices : first one is simply unique(pl_face_vtx), second - one is same size and stores matching vertices. - - Intersection method assumes that 2 matching faces have inverted - face_vtx ordering, but does not necessarily start with same vtx, - ie A B C D -> C' B' A' D'. The idea of the method is to identificate the - starting vertex by sorting the coordinates with the same rule. - Note that a result will be return even if the faces are not truly matching! - """ - pl_vtx_local = np.unique(pl_face_vtx) - pl_vtx_local_opp = np.zeros_like(pl_vtx_local) - - vtx_g_to_l = {v:i for i,v in enumerate(pl_vtx_local)} - - assert len(pl_face_vtx) == len(pld_face_vtx) == pl_face_vtx_idx[-1] - n_face = len(pl_face_vtx_idx) - 1 - n_face_vtx = len(pl_face_vtx) - - received_coords = filter_vtx_coordinates(PT.get_child_from_label(zone, 'GridCoordinates_t'), - PT.get_value(MT.getDistribution(zone, 'Vertex')), - pl_face_vtx, comm) - opp_received_coords = filter_vtx_coordinates(PT.get_child_from_label(zone_d, 'GridCoordinates_t'), - PT.get_value(MT.getDistribution(zone_d, 'Vertex')), - pld_face_vtx, comm) - - #Apply transformation - if PT.GridConnectivity.isperiodic(jn): - perio_vals = PT.GridConnectivity.periodic_values(jn) - opp_received_coords = np_utils.transform_cart_matrix(opp_received_coords.T, - perio_vals.Translation, - perio_vals.RotationCenter, - perio_vals.RotationAngle).T - - - - #Work locally on each original face to find the starting vtx - for iface in range(n_face): - vtx_idx = slice(pl_face_vtx_idx[iface], pl_face_vtx_idx[iface+1]) - - # Unique should sort array following the same key (axis=0 is important!) - _, indices, counts = np.unique(received_coords[vtx_idx], axis = 0, return_index = True, return_counts = True) - _, opp_indices = np.unique(opp_received_coords[vtx_idx], axis = 0, return_index = True) - # Search first unique element (tie breaker) and use it as starting vtx - idx = 0 - counts_it = iter(counts) - while (next(counts_it) != 1): - idx += 1 - first_vtx = indices[idx] - opp_first_vtx = opp_indices[idx] - - ordered_vtx = np_utils.roll_from(pl_face_vtx[vtx_idx], start_idx = first_vtx) - ordered_vtx_opp = np_utils.roll_from(pld_face_vtx[vtx_idx], start_idx = opp_first_vtx, reverse=True) - - pl_vtx_local_opp[[vtx_g_to_l[k] for k in ordered_vtx]] = ordered_vtx_opp - - return pl_vtx_local, pl_vtx_local_opp - -def get_pl_isolated_faces(ngon_node, pl, vtx_distri, comm): - """ - Take a list of face ids and search the faces who are isolated, ie who have no common - edge or vertices with the other faces of the pointlist - Return the array indices of theses faces - """ - pl_face_vtx_idx, pl_face_vtx = face_ids_to_vtx_ids(pl, ngon_node, comm) - PTB = EP.PartToBlock(vtx_distri, [pl_face_vtx], comm, keep_multiple=True) - block_gnum = PTB.getBlockGnumCopy() - vtx_n_occur = PTB.getBlockGnumCountCopy() - - vtx_n_occur_full = np.zeros(vtx_distri[1] - vtx_distri[0], np.int32) - vtx_n_occur_full[block_gnum-vtx_distri[0]-1] = vtx_n_occur - dist_data = {'n_occur' : vtx_n_occur_full} - part_data = EP.block_to_part(dist_data, vtx_distri, [pl_face_vtx], comm) - - #This is the number of total occurence of all the vertices of each face. A face is isolated if each vertex appears - # (globally) only once ie if this total equal the number of vertices of the face - n_vtx_per_face = np.add.reduceat(part_data['n_occur'][0], indices=pl_face_vtx_idx[:-1]) - isolated_face = np.where(n_vtx_per_face == np.diff(pl_face_vtx_idx))[0] - - return isolated_face - -def generate_jn_vertex_list(dist_tree, jn_path, comm): - """ - From a FaceCenter join (given by its path in the tree), create the distributed arrays VertexList - and VertexListDonor such that vertices are matching 1 to 1. - Return the two index arrays and the partial distribution array, which is - identical for both of them - """ - jn = PT.get_node_from_path(dist_tree, jn_path) - assert PT.Subset.GridLocation(jn) == 'FaceCenter' - - base_name, zone_name = jn_path.split('/')[0:2] - zone = PT.get_node_from_path(dist_tree, base_name + '/' + zone_name) - zone_d = PT.get_node_from_path(dist_tree, PT.GridConnectivity.ZoneDonorPath(jn, base_name)) - - ngon_node = PT.Zone.NGonNode(zone) - vtx_distri = PT.get_value(MT.getDistribution(zone, 'Vertex')) - face_distri = PT.get_value(MT.getDistribution(ngon_node, 'Element')) - - ngon_node_d = PT.Zone.NGonNode(zone_d) - vtx_distri_d = PT.get_value(MT.getDistribution(zone_d, 'Vertex')) - face_distri_d = PT.get_value(MT.getDistribution(ngon_node_d, 'Element')) - - distri_jn = PT.get_value(MT.getDistribution(jn, 'Index')) - pl = PT.get_child_from_name(jn, 'PointList' )[1][0] - pl_d = PT.get_child_from_name(jn, 'PointListDonor')[1][0] - - - dn_vtx = [vtx_distri[1] - vtx_distri[0], vtx_distri_d[1] - vtx_distri_d[0]] - dn_face = [face_distri[1] - face_distri[0], face_distri_d[1] - face_distri_d[0]] - - dface_vtx_idx = [shifted_eso(ng) for ng in [ngon_node, ngon_node_d]] - dface_vtx = [as_pdm_gnum(PT.get_node_from_path(ng, 'ElementConnectivity')[1]) for ng in [ngon_node, ngon_node_d]] - - isolated_face_loc = get_pl_isolated_faces(ngon_node, pl, vtx_distri, comm) - not_isolated_face_loc = np.arange(pl.size)[np_utils.others_mask(pl, isolated_face_loc)] - - solo_face = comm.allreduce(isolated_face_loc.size > 0, MPI.LOR) - conn_face = comm.allreduce(not_isolated_face_loc.size > 0, MPI.LOR) - - pl_vtx_l = [] - pld_vtx_l = [] - - if solo_face: - _, pld_face_vtx = face_ids_to_vtx_ids(pl_d, ngon_node_d, comm) - - pl_face_vtx_idx, pl_face_vtx = face_ids_to_vtx_ids(pl, ngon_node, comm) - pl_face_vtx_idx_e, pl_face_vtx_e = np_utils.jagged_extract(pl_face_vtx_idx, pl_face_vtx, isolated_face_loc) - pl_face_vtx_idx_e, pld_face_vtx_e = np_utils.jagged_extract(pl_face_vtx_idx, pld_face_vtx, isolated_face_loc) - pl_vtx_local, pl_vtx_local_opp = \ - _search_with_geometry(zone, zone_d, jn, pl_face_vtx_idx_e, pl_face_vtx_e, pld_face_vtx_e, comm) - pl_vtx_l.append(pl_vtx_local) - pld_vtx_l.append(pl_vtx_local_opp) - - if conn_face: - interface_dn_face = not_isolated_face_loc.size - interface_ids_face = as_pdm_gnum(np_utils.interweave_arrays([pl[not_isolated_face_loc],pl_d[not_isolated_face_loc]])) - - vtx_interfaces = PDM.interface_face_to_vertex(1, #n_interface, - 2, - False, - [interface_dn_face], - [interface_ids_face], - [(0,1)], - dn_vtx, - dn_face, - dface_vtx_idx, - dface_vtx, - comm) - dn_vtx_jn = vtx_interfaces[0]['interface_dn_vtx'] - interface_ids_vtx = vtx_interfaces[0]['np_interface_ids_vtx'] # Can be void because of realloc - - if interface_ids_vtx is not None: - pl_vtx_local = np.copy(interface_ids_vtx[::2]) #Copy is needed to have aligned memory - pld_vtx_local = np.copy(interface_ids_vtx[1::2]) - assert pl_vtx_local.size == dn_vtx_jn - assert pld_vtx_local.size == dn_vtx_jn - else: - pl_vtx_local = np.empty(0, dtype=pdm_dtype) - pld_vtx_local = np.empty(0, dtype=pdm_dtype) - - pl_vtx_l.append(pl_vtx_local) - pld_vtx_l.append(pld_vtx_local) - - #Final part_to_block will merge gnum from two method and reequilibrate - PTB = EP.PartToBlock(None, pl_vtx_l, comm, weight=True, keep_multiple=True) - pl_vtx = PTB.getBlockGnumCopy() - _, pld_vtx = PTB.exchange_field(pld_vtx_l, [np.ones(pl.size, np.int32) for pl in pl_vtx_l]) - assert pld_vtx.size == pl_vtx.size - dn_vtx_jn = pld_vtx.size - distri = par_utils.gather_and_shift(dn_vtx_jn, comm) - distri_jn_vtx = distri[[comm.Get_rank(), comm.Get_rank()+1, comm.Get_size()]] - - return pl_vtx, pld_vtx, distri_jn_vtx - -def _generate_jns_vertex_list(dist_tree, interface_pathes, comm): - """ - Such as generate_jn_vertex_list, create the distributed arrays VertexList - and VertexListDonor such that vertices are matching 1 to 1 from FaceCenter interfaces. - This function manage several interface at the same time, but will no work if isolated faces - (requiring geometric treatment) are present. - """ - - # Collect zones data - zone_to_id = {} - dn_vtx = [] - dn_face = [] - dface_vtx_idx = [] - dface_vtx = [] - for i, zone_path in enumerate(PT.predicates_to_paths(dist_tree, 'CGNSBase_t/Zone_t')): - zone_to_id[zone_path] = i - - zone = PT.get_node_from_path(dist_tree, zone_path) - ngon = PT.Zone.NGonNode(zone) - - face_distri = MT.getDistribution(ngon, 'Element')[1] - vtx_distri = MT.getDistribution(zone, 'Vertex')[1] - - dn_vtx.append(vtx_distri[1] - vtx_distri[0]) - dn_face.append(face_distri[1] - face_distri[0]) - - eso = PT.get_child_from_name(ngon, 'ElementStartOffset')[1] - dface_vtx_idx.append(shifted_eso(ngon)) - dface_vtx.append(as_pdm_gnum(PT.get_child_from_name(ngon, 'ElementConnectivity')[1])) - - # Collect interface data - interface_dn_face = [] - interface_ids_face = [] - interface_dom_face = [] - for interface_path in interface_pathes: - gc = PT.get_node_from_path(dist_tree, interface_path) - pl = PT.get_child_from_name(gc, 'PointList')[1][0] - pld = PT.get_child_from_name(gc, 'PointListDonor')[1][0] - - interface_dn_face.append(pl.size) - interface_ids_face.append(as_pdm_gnum(np_utils.interweave_arrays([pl,pld]))) - cur_zone_path = '/'.join(interface_path.split('/')[:2]) - opp_zone_path = PT.GridConnectivity.ZoneDonorPath(gc, cur_zone_path.split('/')[0]) - interface_dom_face.append((zone_to_id[cur_zone_path], zone_to_id[opp_zone_path])) - - #Call function - vtx_interfaces = PDM.interface_face_to_vertex(len(interface_pathes), - len(zone_to_id), - False, - interface_dn_face, - interface_ids_face, - interface_dom_face, - dn_vtx, - dn_face, - dface_vtx_idx, - dface_vtx, - comm) - - # Unpack results - all_pl_vtx = [] - all_pld_vtx = [] - all_distri_vtx = [] - for vtx_interface in vtx_interfaces: - dn_vtx_jn = vtx_interface['interface_dn_vtx'] - distri = par_utils.gather_and_shift(dn_vtx_jn, comm) - all_distri_vtx.append(distri[[comm.Get_rank(), comm.Get_rank()+1, comm.Get_size()]]) - - interface_ids_vtx = vtx_interface['np_interface_ids_vtx'] # Can be void because of realloc - if interface_ids_vtx is not None: - pl_vtx = np.copy(interface_ids_vtx[::2]) #Copy is needed to have aligned memory - pld_vtx = np.copy(interface_ids_vtx[1::2]) - assert pl_vtx.size == pld_vtx.size == dn_vtx_jn - else: - pl_vtx = np.empty(0, dtype=pdm_dtype) - pld_vtx = np.empty(0, dtype=pdm_dtype) - - all_pl_vtx.append(pl_vtx) - all_pld_vtx.append(pld_vtx) - - return all_pl_vtx, all_pld_vtx, all_distri_vtx - - -def generate_jns_vertex_list(dist_tree, comm, have_isolated_faces=False): - """ - For each 1to1 FaceCenter matching join found in the distributed tree, - create a corresponding 1to1 Vertex matching join. - - Input tree is modified inplace: Vertex ``GridConnectivity_t`` nodes - are stored under distinct containers named from the original ones, suffixed - with `#Vtx`. Similarly, vertex GC nodes uses the original name suffixed - with `#Vtx`. - - Only unstructured-NGon based meshes are supported. - - Args: - dist_tree (CGNSTree): Distributed tree - comm (`MPIComm`) : MPI communicator - have_isolated_faces (bool, optional) : Indicate if original joins includes - faces who does not share any edge with other external (join) faces. - If False, disable the special treatement needed by such faces (better performances, - but will fail if isolated faces were actually present). - Defaults to False. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #generate_jns_vertex_list@start - :end-before: #generate_jns_vertex_list@end - :dedent: 2 - """ - #Build join ids to identify opposite joins - MJT.add_joins_donor_name(dist_tree, comm) - - match_jns = MJT.get_matching_jns(dist_tree, lambda n: PT.Subset.GridLocation(n) == 'FaceCenter') - interface_pathes_cur = [pair[0] for pair in match_jns] - interface_pathes_opp = [pair[1] for pair in match_jns] - - if len(match_jns) == 0: - return - - if have_isolated_faces: - #Filter interfaces having isolated faces; they will be treated one by one, while other will be grouped - have_isolated = [] - for interface_path_cur in interface_pathes_cur: - zone_path = '/'.join(interface_path_cur.split('/')[:2]) - zone_node = PT.get_node_from_path(dist_tree, zone_path) - ngon_node = PT.Zone.NGonNode(zone_node) - n_isolated = get_pl_isolated_faces(ngon_node, - PT.get_node_from_path(dist_tree, interface_path_cur + '/PointList')[1][0], - MT.getDistribution(zone_node, 'Vertex')[1], - comm).size - have_isolated.append(bool(comm.allreduce(n_isolated, MPI.SUM) > 0)) - - itrf_cur_with_iso = [itrf for i,itrf in enumerate(interface_pathes_cur) if have_isolated[i]] - itrf_cur_without_iso = [itrf for i,itrf in enumerate(interface_pathes_cur) if not have_isolated[i]] - - itrf_opp_with_iso = [itrf for i,itrf in enumerate(interface_pathes_opp) if have_isolated[i]] - itrf_opp_without_iso = [itrf for i,itrf in enumerate(interface_pathes_opp) if not have_isolated[i]] - else: - itrf_cur_with_iso = [] - itrf_cur_without_iso = interface_pathes_cur - itrf_opp_with_iso = [] - itrf_opp_without_iso = interface_pathes_opp - - interface_pathes_cur = itrf_cur_without_iso + itrf_cur_with_iso - interface_pathes_opp = itrf_opp_without_iso + itrf_opp_with_iso - - all_pl_vtx, all_pld_vtx, all_distri_vtx = _generate_jns_vertex_list(dist_tree, itrf_cur_without_iso, comm) - for interface_path_cur in itrf_cur_with_iso: - r = generate_jn_vertex_list(dist_tree, interface_path_cur, comm) - for j, l in enumerate([all_pl_vtx, all_pld_vtx, all_distri_vtx]): - l.append(r[j]) - - - # Create in tree - for i, interface_path in enumerate(zip(interface_pathes_cur, interface_pathes_opp)): - pl_vtx, pl_vtx_opp, distri_jn = all_pl_vtx[i], all_pld_vtx[i], all_distri_vtx[i] #Get results - for j, gc_path in enumerate(interface_path): - base_name, zone_name, zgc_name, gc_name = gc_path.split('/') - zone = PT.get_node_from_path(dist_tree, base_name + '/' + zone_name) - zgc = PT.get_child_from_name(zone, zgc_name) - gc = PT.get_node_from_path(dist_tree, gc_path) - - if j == 1: #Swap pl/pld for opposite jn - pl_vtx, pl_vtx_opp = pl_vtx_opp, pl_vtx - jn_vtx = PT.new_GridConnectivity(PT.get_name(gc)+'#Vtx', PT.get_value(gc), \ - loc='Vertex', type='Abutting1to1', parent=zgc) - PT.new_IndexArray('PointList', pl_vtx.reshape(1,-1), parent=jn_vtx) - PT.new_IndexArray('PointListDonor', pl_vtx_opp.reshape(1,-1), parent=jn_vtx) - MT.newDistribution({'Index' : distri_jn}, jn_vtx) - - PT.add_child(jn_vtx, PT.get_child_from_label(gc, 'GridConnectivityProperty_t')) - PT.add_child(jn_vtx, PT.get_child_from_name(gc, 'DistInterfaceId')) - PT.add_child(jn_vtx, PT.get_child_from_name(gc, 'DistInterfaceOrd')) - donor_name_node = PT.get_child_from_name(gc, 'GridConnectivityDonorName') - if donor_name_node is not None: - PT.new_node('GridConnectivityDonorName', 'Descriptor_t', \ - PT.get_value(donor_name_node)+'#Vtx', parent=jn_vtx) - - diff --git a/maia/algo/indexing.py b/maia/algo/indexing.py deleted file mode 100644 index 79381ea3..00000000 --- a/maia/algo/indexing.py +++ /dev/null @@ -1,80 +0,0 @@ -import numpy as np - -import maia.pytree as PT -import maia.pytree.sids as sids -from maia.algo.apply_function_to_nodes import zones_iterator - -def get_ngon_pe_local(ngon_node): - """ - Shift the ParentElement array of a NGonNode to have local (starting at 1) cell - indices. - If PE array was already local, no copy is done - """ - assert sids.Element.CGNSName(ngon_node) == 'NGON_n' - pe_n = PT.get_child_from_name(ngon_node, "ParentElements") - if pe_n is None: - raise RuntimeError(f"ParentElements node not found on ngon node {ngon_node[0]}") - pe_val = pe_n[1] - if pe_val.size == 0: - return pe_val - else: - first_cell = np.max(pe_val[1]) #Get any cell and use it to check if offset is necessary - if first_cell > sids.Element.Range(ngon_node)[1]: - return pe_val - sids.Element.Range(ngon_node)[1] * (pe_val > 0) - else: - return pe_val - -def pe_to_nface(t, comm=None, removePE=False): - """Create a NFace node from a NGon node with ParentElements. - - Input tree is modified inplace. - - Args: - t (CGNSTree(s)): Distributed or Partitioned tree (or sequences of) - starting at Zone_t level or higher. - comm (MPIComm) : MPI communicator, mandatory only for distributed zones - remove_PE (bool, optional): If True, remove the ParentElements node. - Defaults to False. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #pe_to_nface@start - :end-before: #pe_to_nface@end - :dedent: 2 - """ - for zone in zones_iterator(t): - if PT.maia.getDistribution(zone) is not None: - assert comm is not None - from .dist.ngon_tools import pe_to_nface - pe_to_nface(zone, comm, removePE) - else: - from .part.ngon_tools import pe_to_nface - pe_to_nface(zone, removePE) - - -def nface_to_pe(t, comm=None, removeNFace=False): - """Create a ParentElements node in the NGon node from a NFace node. - - Input tree is modified inplace. - - Args: - t (CGNSTree(s)): Distributed or Partitioned tree (or sequences of) - starting at Zone_t level or higher. - comm (MPIComm) : MPI communicator, mandatory only for distributed zones - removeNFace (bool, optional): If True, remove the NFace node. - Defaults to False. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #nface_to_pe@start - :end-before: #nface_to_pe@end - :dedent: 2 - """ - for zone in zones_iterator(t): - if PT.maia.getDistribution(zone) is not None: - assert comm is not None - from .dist.ngon_tools import nface_to_pe - nface_to_pe(zone, comm, removeNFace) - else: - from .part.ngon_tools import nface_to_pe - nface_to_pe(zone, removeNFace) diff --git a/maia/algo/part/__init__.py b/maia/algo/part/__init__.py deleted file mode 100644 index 733c404e..00000000 --- a/maia/algo/part/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Algorithms for partitioned trees -""" - -from .closest_points import find_closest_points - -from .connectivity_transform import enforce_boundary_pe_left - -from .extract_boundary import extract_faces_mesh,\ - extract_surf_from_bc - -from .extract_part import extract_part_from_bc_name,\ - extract_part_from_family,\ - extract_part_from_zsr - -from .geometry import compute_cell_center,\ - compute_edge_center,\ - compute_face_center - -from .interpolate import interpolate_from_part_trees,\ - create_interpolator_from_part_trees - -from .isosurf import iso_surface,\ - plane_slice,\ - spherical_slice - -from .localize import localize_points - -from .move_loc import centers_to_nodes,\ - nodes_to_centers,\ - CenterToNode,\ - NodeToCenter - -from .wall_distance import compute_wall_distance - diff --git a/maia/algo/part/cgns_registry/cgns_registry.cpp b/maia/algo/part/cgns_registry/cgns_registry.cpp deleted file mode 100644 index ce44c0a5..00000000 --- a/maia/algo/part/cgns_registry/cgns_registry.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include - -#include "maia/algo/part/cgns_registry/cgns_registry.hpp" - - -// =========================================================================== -cgns_registry::cgns_registry(const cgns_paths_by_label& paths_by_label, MPI_Comm comm) { - for (int i=0; i < CGNS::nb_cgns_labels; ++i){ - // std::cout << to_string(static_cast(i)) <(reg.distribution()[i])) + " "; - } - s += "\n"; - return s; -} - -// =========================================================================== -std::string to_string(const cgns_registry& cgns_reg) -{ - std::string s; - for (int i = 0; i < CGNS::nb_cgns_labels; ++i){ - int n_entry = cgns_reg.at(i).nb_entities(); - if (n_entry > 0) { - s += " -------------------------------------------- \n"; - s += " ### CGNSLabel : " +to_string(static_cast(i))+"\n"; - s += to_string(cgns_reg.at(i)); - } - } - return s; -} - -// =========================================================================== -PDM_g_num_t get_global_id_from_path(const label_registry& reg, std::string path) { - return reg.find_id_from_entity(path); -} -std::string get_path_from_global_id(const label_registry& reg, PDM_g_num_t g_id) { - return reg.find_entity_from_id(g_id); -} - -// =========================================================================== -PDM_g_num_t get_global_id_from_path_and_type(const cgns_registry& cgns_reg, std::string path, CGNS::Label label){ - return get_global_id_from_path(cgns_reg.at(label),path); -} - -// =========================================================================== -std::string get_path_from_global_id_and_type(const cgns_registry& cgns_reg, PDM_g_num_t g_id, CGNS::Label label){ - return get_path_from_global_id(cgns_reg.at(label), g_id); -} - -// =========================================================================== -PDM_g_num_t get_global_id_from_path_and_type(const cgns_registry& cgns_reg, std::string path, std::string cgns_label_str){ - auto label = std_e::to_enum(cgns_label_str); - return get_global_id_from_path_and_type(cgns_reg,path,label); -} - -// =========================================================================== -std::string get_path_from_global_id_and_type(const cgns_registry& cgns_reg, PDM_g_num_t g_id, std::string cgns_label_str){ - auto label = std_e::to_enum(cgns_label_str); - return get_path_from_global_id_and_type(cgns_reg,g_id,label); -} diff --git a/maia/algo/part/cgns_registry/cgns_registry.hpp b/maia/algo/part/cgns_registry/cgns_registry.hpp deleted file mode 100644 index f4cb8086..00000000 --- a/maia/algo/part/cgns_registry/cgns_registry.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include "pdm.h" - -#include "maia/pytree/cgns_keywords/cgns_keywords.hpp" -#include "maia/algo/part/cgns_registry/distributed_registry.hpp" - - -using cgns_path = std::string; -using cgns_paths = std::vector; -using label_registry = distributed_registry; - -using cgns_paths_by_label = std::array; - -inline -void add_path(cgns_paths_by_label& paths, const cgns_path& path, CGNS::Label label) { - paths[label].push_back(std::move(path)); -} -inline -void add_path(cgns_paths_by_label& paths, const std::string& path, const std::string& label_str) { - auto label = std_e::to_enum(label_str); - add_path(paths,path,label); -} - - -// =========================================================================== -class cgns_registry { - public: - cgns_registry() = default; - cgns_registry(const cgns_paths_by_label& paths, MPI_Comm comm); - - const label_registry& at(int label) const { - return registries_by_label[label]; - } - - const std::vector& paths(int label) const { - return registries_by_label[label].entities(); - } - - const std::vector& global_ids(int label) const { - return registries_by_label[label].ids(); - } - - const distribution_vector& distribution(int label) const { - return registries_by_label[label].distribution(); - } - private: - std::array registries_by_label; -}; - -// =========================================================================== -PDM_g_num_t get_global_id_from_path(const label_registry& reg, std::string path); -std::string get_path_from_global_id(const label_registry& reg, PDM_g_num_t g_id); - -// =========================================================================== -PDM_g_num_t get_global_id_from_path_and_type(const cgns_registry& cgns_reg, std::string path, CGNS::Label label); -std::string get_path_from_global_id_and_type(const cgns_registry& cgns_reg, PDM_g_num_t g_id, CGNS::Label label); - -PDM_g_num_t get_global_id_from_path_and_type(const cgns_registry& cgns_reg, std::string path, std::string cgns_label_str); -std::string get_path_from_global_id_and_type(const cgns_registry& cgns_reg, PDM_g_num_t g_id, std::string cgns_label_str); - -// =========================================================================== -std::string to_string(const cgns_registry& cgns_reg); diff --git a/maia/algo/part/cgns_registry/cgns_registry.pybind.cpp b/maia/algo/part/cgns_registry/cgns_registry.pybind.cpp deleted file mode 100644 index e83f57bb..00000000 --- a/maia/algo/part/cgns_registry/cgns_registry.pybind.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include -#include -#include "maia/utils/parallel/mpi4py.hpp" -#include "maia/algo/part/cgns_registry/cgns_registry.hpp" - -namespace py = pybind11; - -cgns_registry make_cgns_registry(const cgns_paths_by_label& paths, py::object mpi4py_obj){ - return cgns_registry(paths, maia::mpi4py_comm_to_comm(mpi4py_obj)); -} - -void register_cgns_registry_module(py::module_& parent) { - - py::module_ m = parent.def_submodule("cgns_registry"); - - m.doc() = "pybind11 utils for cgns_registry plugin"; // optional module docstring - - py::class_ (m, "cgns_paths_by_label") - .def(py::init<>()); - - py::bind_vector>(m, "cgns_paths"); // Neccesary to return into python pybind11 : ticket 2641 - py::bind_vector>(m, "global_ids"); - - py::class_ (m, "cgns_registry") - .def(py::init<>(&make_cgns_registry)) - .def("at" , &cgns_registry::at , py::return_value_policy::automatic_reference) - .def("paths" , &cgns_registry::paths , py::return_value_policy::automatic_reference) - .def("global_ids" , &cgns_registry::global_ids , py::return_value_policy::automatic_reference) - .def("distribution", &cgns_registry::distribution, py::return_value_policy::automatic_reference) - .def("__repr__", [](const cgns_registry& x){ - return to_string(x); - }); - - m.def("add_path", - py::overload_cast(add_path), - "Some doc here"); - - m.def("get_global_id_from_path_and_type", - py::overload_cast(get_global_id_from_path_and_type), - "Some doc here"); - - m.def("get_path_from_global_id_and_type", - py::overload_cast(get_path_from_global_id_and_type), - "Some doc here"); - - m.def("get_path_from_global_id_and_type", [](const cgns_registry& cgns_reg, int g_id, CGNS::Label label) { - return get_path_from_global_id_and_type(cgns_reg, static_cast(g_id), label); - }); - - m.def("get_global_id_from_path_and_type", - py::overload_cast(get_global_id_from_path_and_type), - "Some doc here"); - - m.def("get_path_from_global_id_and_type", - py::overload_cast(get_path_from_global_id_and_type), - "Some doc here"); - - m.def("get_path_from_global_id_and_type", [](const cgns_registry& cgns_reg, int g_id, std::string cgns_label_str) { - return get_path_from_global_id_and_type(cgns_reg, static_cast(g_id), cgns_label_str); - }); - -} diff --git a/maia/algo/part/cgns_registry/cgns_registry.pybind.hpp b/maia/algo/part/cgns_registry/cgns_registry.pybind.hpp deleted file mode 100644 index b2944117..00000000 --- a/maia/algo/part/cgns_registry/cgns_registry.pybind.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#include - -void register_cgns_registry_module(pybind11::module_& parent); - diff --git a/maia/algo/part/cgns_registry/distributed_registry.hpp b/maia/algo/part/cgns_registry/distributed_registry.hpp deleted file mode 100644 index 790cf373..00000000 --- a/maia/algo/part/cgns_registry/distributed_registry.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - - -#include "std_e/data_structure/table.hpp" -#include "std_e/parallel/mpi.hpp" -#include "maia/utils/parallel/distribution.hpp" -#include "maia/algo/part/cgns_registry/generate_global_numbering_from_paths.hpp" - -// TODO unit tests -// TODO replace int by PDM_g_id_t ? - -template auto // TODO RENAME -create_sorted_id_table(std::vector entities, MPI_Comm comm) -> std_e::table { - std::vector integer_ids = generate_global_numbering(entities,comm); - std_e::table id_table(std::move(integer_ids),std::move(entities)); - std_e::sort(id_table); - return id_table; -} - -inline auto -generate_distribution(const std::vector& sorted_local_ids, MPI_Comm comm) -> distribution_vector { - // precondition: Union(sorted_local_ids) over comm == [0,global_nb_elts) - PDM_g_num_t local_max_id = 0; - if (sorted_local_ids.size() > 0) { - local_max_id = sorted_local_ids.back(); - } - PDM_g_num_t global_nb_elts = std_e::max_global(local_max_id,comm); - return uniform_distribution(std_e::n_rank(comm),global_nb_elts+1); -} - - -// Generate a global numbering and distribution from entities -template -class distributed_registry { // TODO RENAME partitionned_registry - // Class invariants - // - entities and ids are both sorted by increasing ids - // - Union(ids) on comm == [0,global_nb_entities) - // - Sum(nb_entities()) on comm <= global_nb_entities (entities can be on more than one rank) - public: - distributed_registry() = default; - distributed_registry(std::vector entities, MPI_Comm comm) - : id_table(create_sorted_id_table(std::move(entities),comm)) - , distrib(generate_distribution(ids(),comm)) - {} - - int nb_entities() const { - return id_table.size(); - } - const auto& ids() const { - return std_e::range<0>(id_table); - } - const auto& entities() const { - return std_e::range<1>(id_table); - } - const auto& distribution() const { - return distrib; - } - - auto - find_entity_from_id(PDM_g_num_t id) const -> const T& { - return find_associate(id_table,id); - } - auto - find_id_from_entity(const T& e) const -> PDM_g_num_t { - return find_associate(id_table,e); - } - private: - std_e::table id_table; - distribution_vector distrib; -}; diff --git a/maia/algo/part/cgns_registry/generate_global_numbering_from_paths.cpp b/maia/algo/part/cgns_registry/generate_global_numbering_from_paths.cpp deleted file mode 100644 index e74f20f0..00000000 --- a/maia/algo/part/cgns_registry/generate_global_numbering_from_paths.cpp +++ /dev/null @@ -1,248 +0,0 @@ -#include "maia/algo/part/cgns_registry/generate_global_numbering_from_paths.hpp" - -#include -#include -#include -#include -#include -#include "maia/utils/parallel/distribution.hpp" -#include "std_e/interval/interval_sequence.hpp" -#include "std_e/parallel/mpi.hpp" -// #include "logging/logging.hpp" -#include "std_e/utils/vector.hpp" -#include "std_e/algorithm/algorithm.hpp" -#include "std_e/logging/log.hpp" -// #include "pdm_sort.h" -// #include "pdm_gnum_from_hash_values.h" -// ------------------------------------------------------------------ -// - -// =========================================================================== -std::vector generate_global_id( int n_loc_id, - const std::vector& block_paths, - const std::vector& send_n, - const std_e::interval_vector& send_idx, - const std::vector& recv_n, - const std_e::interval_vector& recv_idx, - MPI_Comm comm) -{ - // ------------------------------------------------------------------- - // 1 - Identify an order without touching the original ordering ---> Preserve send/recv - int n_block = block_paths.size(); - std::vector order_name(n_block); - std::iota(begin(order_name), end(order_name), 0); - std::sort(begin(order_name), end(order_name), [&](const int& i1, const int& i2){ - if(std::hash{}(block_paths[i1]) == std::hash{}(block_paths[i2])){ - return block_paths[i1] < block_paths[i2]; - } else { - return std::hash{}(block_paths[i1]) < std::hash{}(block_paths[i2]); - } - }); - - // if(n_block > 0) { - // printf("n_block = %i \n", n_block); - // } - - // ------------------------------------------------------------------- - // 2 - Give an local number for each element in block_paths - std::vector global_name_num(n_block); - PDM_g_num_t next_name_id = 0; - PDM_g_num_t n_loc_name_id = 0; - std::string lastName; - for(int i = 0; i < n_block; ++i){ - if(block_paths[order_name[i]] == lastName){ - global_name_num[order_name[i]] = next_name_id; - } else { - next_name_id++; - n_loc_name_id++; - global_name_num[order_name[i]] = next_name_id; - lastName = block_paths[order_name[i]]; - } - } - - // ------------------------------------------------------------------- - // 3 - Setup global numbering by simply shift - PDM_g_num_t shift_g; - PDM_MPI_Comm pdm_comm = PDM_MPI_mpi_2_pdm_mpi_comm(&comm); - int ierr = PDM_MPI_Scan(&n_loc_name_id, &shift_g, 1, PDM__PDM_MPI_G_NUM, PDM_MPI_SUM, pdm_comm); - assert(ierr == 0); - shift_g -= n_loc_name_id; - - for(int i = 0; i < n_block; ++i){ - global_name_num[i] += shift_g; - } - - // Panic verbose - // for(int i = 0; i < n_block; ++i) { - // std::string s; - // s += block_paths[i] + " --> " + std::to_string(global_name_num[i]) + " - order= " + std::to_string(order_name[i]) + " \n"; - // std_e::log("sonics", s); - // } - - // ------------------------------------------------------------------- - // 4 - Back to original distribution - std::vector part_global_id(n_loc_id); - PDM_MPI_Alltoallv(global_name_num.data(), (int *) recv_n.data(), (int *) recv_idx.data(), PDM__PDM_MPI_G_NUM, - part_global_id.data() , (int *) send_n.data(), (int *) send_idx.data(), PDM__PDM_MPI_G_NUM, pdm_comm); - - //std_e::offset(part_global_id,-1); - return part_global_id; -} - -// =========================================================================== -// On cherche a creer une numerotation absolue plutot... -std::vector generate_global_numbering(/* TODO const */ std::vector& part_paths, - MPI_Comm comm) -{ - int n_rank = std_e::n_rank(comm); - - // ------------------------------------------------------------------- - std_e::sort_unique(part_paths); - int n_loc_id = part_paths.size(); - - // ------------------------------------------------------------------- - // 1 - Generate keys from path - // DOC we use a hash for two reasons: - // - we need a fast way to generate a reasonably balanced distribution of all unique path into slots (here, the mpi ranks) - // since hash collisions are rare, chances of many different paths having the same hash, and hence being distributed on the same slot, is also rare - // - equal paths have equal hashes, so they will end up in the same mpi rank, so generating a unique global id is then local to the rank - // we don't mind that different paths may have the same hash: they will be distributed on the same slot, but then they are treated separatly - std::vector part_paths_code = std_e::hash_vector(part_paths); - - // for(int i = 0; i < part_paths_code.size(); ++i) { - // std::string s; - // s += "part_paths = " + part_paths[i] + " --> " + std::to_string(part_paths_code[i]) + " \n"; - // std_e::log("sonics", s); - // } - - // ------------------------------------------------------------------- - // 2 - Generate distribution - // DOC we make the hypothesis that the hashes repartition will be almost uniform in [0,2^64) - // TODO Repartion from sampling ( - distribution_vector distrib_key = uniform_distribution(n_rank, std::numeric_limits::max()); - - //// ------------------------------------------------------------------- - //// 3 - TODO Not used -- Store for each string their size in order to make strideBuffer on char - //std::vector part_pathsSize(n_loc_id); - //for(int i = 0; i < n_loc_id; ++i){ - // part_pathsSize[i] = static_cast(part_paths[i].size()); - //} - - // ------------------------------------------------------------------- - // 4 - Prepare buffer send - std::vector send_n(n_rank); - std::vector recv_n(n_rank); - std::vector send_str_n(n_rank); - std::vector recv_str_n(n_rank); - std::fill(begin(send_n ), end(send_n ), 0); - std::fill(begin(send_str_n), end(send_str_n), 0); - - assert(static_cast(part_paths .size()) == n_loc_id); - assert(static_cast(part_paths_code.size()) == n_loc_id); - - for(int i = 0; i < n_loc_id; ++i){ - // std::cout << "part_paths_code[" << i << "/" << part_paths_code.size() << "]" << distrib_key.size() << std::endl; - int i_rank = search_rank(part_paths_code[i], distrib_key); - assert(i_rank >= 0); - int n_data_to_send = sizeof(char) * part_paths[i].size(); - send_str_n[i_rank] += n_data_to_send; - send_n[i_rank]++; - } - - // ------------------------------------------------------------------- - // Panic verbose - // for(int i = 0; i < n_rank; ++i){ - // e_log << "send_n [" << i << "] = " << send_n[i] << std::endl; - // e_log << "send_str_n[" << i << "] = " << send_str_n[i] << std::endl; - // } - // ------------------------------------------------------------------- - - // ------------------------------------------------------------------- - // 5 - Exchange - MPI_Alltoall(send_n .data(), 1, MPI_INT, recv_n .data(), 1, MPI_INT, comm); - MPI_Alltoall(send_str_n.data(), 1, MPI_INT, recv_str_n.data(), 1, MPI_INT, comm); - - // ------------------------------------------------------------------- - // Panic verbose - // for(int i = 0; i < n_rank; ++i){ - // e_log << "recv_n [" << i << "] = " << recv_n[i] << std::endl; - // e_log << "recv_str_n[" << i << "] = " << recv_str_n[i] << std::endl; - // } - // ------------------------------------------------------------------- - - // ------------------------------------------------------------------- - // 6 - Compute all index (need for MPI and algorithm) - std_e::interval_vector send_idx = std_e::indices_from_strides(send_n ); - std_e::interval_vector recv_idx = std_e::indices_from_strides(recv_n ); - std_e::interval_vector send_str_idx = std_e::indices_from_strides(send_str_n); - std_e::interval_vector recv_str_idx = std_e::indices_from_strides(recv_str_n); - - // ------------------------------------------------------------------- - // 7 - Allocation of buffer - std::vector send_buffer (send_idx .back()); - std::vector recv_buffer (recv_idx .back()); - std::vector send_str_buffer(send_str_idx.back()); - std::vector recv_str_buffer(recv_str_idx.back()); - - // ------------------------------------------------------------------- - // 8 - Fill buffer send // TODO serialize? - std::vector send_count (n_rank); - std::vector send_str_count(n_rank); - std::fill(begin(send_count ), end(send_count ), 0); - std::fill(begin(send_str_count), end(send_str_count), 0); - for(int i = 0; i < n_loc_id; ++i){ - int i_rank_to_send = search_rank(part_paths_code[i], distrib_key); - int string_size = part_paths[i].size(); - send_buffer[send_idx[i_rank_to_send]+send_count[i_rank_to_send]++] = string_size; - for(int j = 0; j < string_size; ++j){ - send_str_buffer[send_str_idx[i_rank_to_send]+send_str_count[i_rank_to_send]++] = part_paths[i][j]; - } - } - - // ------------------------------------------------------------------- - // 9 - Exchange - MPI_Alltoallv(send_buffer.data(), send_n.data(), send_idx.data(), MPI_INT, - recv_buffer.data(), recv_n.data(), recv_idx.data(), MPI_INT, comm); - MPI_Alltoallv(send_str_buffer.data(), send_str_n.data(), send_str_idx.data(), MPI_BYTE, - recv_str_buffer.data(), recv_str_n.data(), recv_str_idx.data(), MPI_BYTE, comm); - - // ------------------------------------------------------------------- - // 10 - Post-treat exchange and rebuild string - int n_string_to_recv = std::accumulate(begin(recv_n), end(recv_n), 0); - std::vector block_paths(n_string_to_recv); - int idxG = 0; - for(int i = 0; i < n_rank; ++i){ - int beg_recv = recv_idx [i]; - int beg_recv_str = recv_str_idx[i]; - // e_log << " Recv from " << i << " at " << beg_recv << std::endl; - // for(int idx_data = 0; idx_data < recv_n[i]; idx_data++){ - // e_log << " Recv_buffer[" << beg_recv+idx_data << "] = " << recv_buffer[beg_recv+idx_data] << std::endl; - // } - int idx_str = 0; - for(int idx_data = 0; idx_data < recv_n[i]; idx_data++){ - int size_name_recv = recv_buffer[beg_recv+idx_data]; - for(int j = 0; j < size_name_recv; ++j){ - block_paths[idxG] += recv_str_buffer[beg_recv_str+idx_str++]; - } - // e_log << "block_paths[" << idxG << "] = " << block_paths[idxG] << std::endl; - idxG++; - } - } - - // ------------------------------------------------------------------- - // 11 - Order - std::vector recv_global_id = generate_global_id(n_loc_id, block_paths, send_n, send_idx, recv_n, recv_idx, comm); - - // Revert buffer - std::vector part_global_id(n_loc_id); - std::fill(begin(send_n ), end(send_n ), 0); - for(int i = 0; i < n_loc_id; ++i){ - int i_rank = search_rank(part_paths_code[i], distrib_key); - assert(i_rank >= 0); - - int idx_read = send_idx[i_rank] + send_n[i_rank]++; - part_global_id[i] = recv_global_id[idx_read]; - } - - return part_global_id; -} diff --git a/maia/algo/part/cgns_registry/generate_global_numbering_from_paths.hpp b/maia/algo/part/cgns_registry/generate_global_numbering_from_paths.hpp deleted file mode 100644 index ee146347..00000000 --- a/maia/algo/part/cgns_registry/generate_global_numbering_from_paths.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include -#include -#include -#include "pdm.h" - -std::vector generate_global_numbering(/* TODO const */ std::vector& partPaths, - MPI_Comm comm); diff --git a/maia/algo/part/cgns_registry/test/cgns_registry.test.cpp b/maia/algo/part/cgns_registry/test/cgns_registry.test.cpp deleted file mode 100644 index 5c79fc9a..00000000 --- a/maia/algo/part/cgns_registry/test/cgns_registry.test.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "mpi.h" -#include "doctest/extensions/doctest_mpi.h" -#include "maia/algo/part/cgns_registry/cgns_registry.hpp" - - - -// --------------------------------------------------------------------------------- -constexpr auto cfg1_path_base = "/cgns_base"; -constexpr auto cfg1_path_zone1 = "/cgns_base/Zone1"; -constexpr auto cfg1_path_zone2 = "/cgns_base/Zone2"; -constexpr auto cfg1_path_zone1_j1 = "/cgns_base/Zone1/ZoneGridConnectivity/Join1"; -constexpr auto cfg1_path_zone2_j2 = "/cgns_base/Zone2/ZoneGridConnectivity/Join2"; - -// --------------------------------------------------------------------------------- -inline -cgns_paths_by_label generate_case_1_1_proc(){ - cgns_paths_by_label cgns_paths; - - add_path(cgns_paths, cfg1_path_base, CGNS::Label::CGNSBase_t); - - add_path(cgns_paths, cfg1_path_zone1, CGNS::Label::Zone_t); - add_path(cgns_paths, cfg1_path_zone2, CGNS::Label::Zone_t); - - add_path(cgns_paths, cfg1_path_zone1_j1, CGNS::Label::GridConnectivity1to1_t); - add_path(cgns_paths, cfg1_path_zone2_j2, CGNS::Label::GridConnectivity1to1_t); - - return cgns_paths; -} - - -// --------------------------------------------------------------------------------- -inline -cgns_paths_by_label generate_case_2_1_proc(){ - cgns_paths_by_label cgns_paths; - - add_path(cgns_paths, "Base0/ZoneU1/ZoneBC/FARFIELD", CGNS::Label::BC_t); - add_path(cgns_paths, "Base0/ZoneU1/ZoneBC/WALL" , CGNS::Label::BC_t); - add_path(cgns_paths, "Base0/ZoneU2/ZoneBC/SYM" , CGNS::Label::BC_t); - add_path(cgns_paths, "Base0/ZoneU2/ZoneBC/WALL" , CGNS::Label::BC_t); - - return cgns_paths; -} - -// --------------------------------------------------------------------------------- -inline -cgns_paths_by_label generate_case_1_2_proc(MPI_Comm& comm){ - cgns_paths_by_label cgns_paths; - - int n_rank_l, i_rank_l; - MPI_Comm_size(comm, &n_rank_l); - MPI_Comm_rank(comm, &i_rank_l); - assert(n_rank_l == 2); - - if( i_rank_l == 0) { - add_path(cgns_paths, cfg1_path_base , CGNS::Label::CGNSBase_t); - add_path(cgns_paths, cfg1_path_zone1 , CGNS::Label::Zone_t); - add_path(cgns_paths, cfg1_path_zone1_j1, CGNS::Label::GridConnectivity1to1_t); - } else { - add_path(cgns_paths, cfg1_path_base , CGNS::Label::CGNSBase_t); - add_path(cgns_paths, cfg1_path_zone2 , CGNS::Label::Zone_t); - add_path(cgns_paths, cfg1_path_zone2_j2, CGNS::Label::GridConnectivity1to1_t); - } - - return cgns_paths; -} - -// --------------------------------------------------------------------------------- -MPI_TEST_CASE("[cgns_registry] Generate global numbering [1 proc]",1) { - cgns_paths_by_label conf_1 = generate_case_1_1_proc(); - - cgns_registry cgns_reg1 = cgns_registry(conf_1, test_comm); - - /* CGNS Path have the same id around all procs */ - int g_id_base = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_base , CGNS::Label::CGNSBase_t ); - int g_id_zone1 = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_zone1 , CGNS::Label::Zone_t ); - int g_id_zone2 = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_zone2 , CGNS::Label::Zone_t ); - int g_id_join1 = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_zone1_j1, CGNS::Label::GridConnectivity1to1_t); - int g_id_join2 = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_zone2_j2, CGNS::Label::GridConnectivity1to1_t); - - CHECK(g_id_base == 1); - CHECK(g_id_zone1 == 1); - CHECK(g_id_zone2 == 2); - CHECK(g_id_join1 == 1); - CHECK(g_id_join2 == 2); - -} - -// --------------------------------------------------------------------------------- -MPI_TEST_CASE("[cgns_registry] get_path_from_global_id_and_type [1 proc] ",1) { - cgns_paths_by_label conf_2 = generate_case_2_1_proc(); - - cgns_registry cgns_reg1 = cgns_registry(conf_2, test_comm); - - std::string b1_path = get_path_from_global_id_and_type(cgns_reg1, 1, CGNS::Label::BC_t); - std::string b2_path = get_path_from_global_id_and_type(cgns_reg1, 2, CGNS::Label::BC_t); - std::string b3_path = get_path_from_global_id_and_type(cgns_reg1, 3, CGNS::Label::BC_t); - std::string b4_path = get_path_from_global_id_and_type(cgns_reg1, 4, CGNS::Label::BC_t); - - CHECK(b1_path == "Base0/ZoneU1/ZoneBC/FARFIELD"); - CHECK(b2_path == "Base0/ZoneU1/ZoneBC/WALL" ); - CHECK(b3_path == "Base0/ZoneU2/ZoneBC/SYM" ); - CHECK(b4_path == "Base0/ZoneU2/ZoneBC/WALL" ); - -} - - - - - -// --------------------------------------------------------------------------------- -MPI_TEST_CASE("[cgns_registry] Generate global numbering [2 proc]",2) { - cgns_paths_by_label conf_1 = generate_case_1_2_proc(test_comm); - - cgns_registry cgns_reg1 = cgns_registry(conf_1, test_comm); - - int n_rank_l, i_rank_l; - MPI_Comm_size(test_comm, &n_rank_l); - MPI_Comm_rank(test_comm, &i_rank_l); - - int g_id_base = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_base , CGNS::Label::CGNSBase_t); - - DOCTEST_MPI_CHECK(0, g_id_base == 1); - DOCTEST_MPI_CHECK(1, g_id_base == 1); - - std::string b_path = get_path_from_global_id_and_type(cgns_reg1, g_id_base, CGNS::Label::CGNSBase_t); - - DOCTEST_MPI_CHECK(0, b_path == cfg1_path_base); - DOCTEST_MPI_CHECK(1, b_path == cfg1_path_base); - - - if(i_rank_l == 0){ - - int g_id_zone1 = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_zone1 , CGNS::Label::Zone_t ); - int g_id_join1 = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_zone1_j1, CGNS::Label::GridConnectivity1to1_t); - - DOCTEST_MPI_CHECK(0, g_id_zone1 == 1); - DOCTEST_MPI_CHECK(0, g_id_join1 == 1); - - std::string z1_path = get_path_from_global_id_and_type(cgns_reg1, g_id_zone1, CGNS::Label::Zone_t); - std::string j1_path = get_path_from_global_id_and_type(cgns_reg1, g_id_join1, CGNS::Label::GridConnectivity1to1_t); - - DOCTEST_MPI_CHECK(0, z1_path == cfg1_path_zone1 ); - DOCTEST_MPI_CHECK(0, j1_path == cfg1_path_zone1_j1); - - } else { - - int g_id_zone2 = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_zone2 , CGNS::Label::Zone_t ); - int g_id_join2 = get_global_id_from_path_and_type(cgns_reg1, cfg1_path_zone2_j2, CGNS::Label::GridConnectivity1to1_t); - - DOCTEST_MPI_CHECK(1, g_id_zone2 == 2); - DOCTEST_MPI_CHECK(1, g_id_join2 == 2); - - std::string z2_path = get_path_from_global_id_and_type(cgns_reg1, g_id_zone2, CGNS::Label::Zone_t); - std::string j2_path = get_path_from_global_id_and_type(cgns_reg1, g_id_join2, CGNS::Label::GridConnectivity1to1_t); - - DOCTEST_MPI_CHECK(1, z2_path == cfg1_path_zone2 ); - DOCTEST_MPI_CHECK(1, j2_path == cfg1_path_zone2_j2); - - } - -} - - -// --------------------------------------------------------------------------------- -MPI_TEST_CASE("[cgns_registry] Generate global numbering for zone [1 proc]",1) { - cgns_paths_by_label cgns_paths; - - add_path(cgns_paths, "Base0/ZoneU1" , CGNS::Label::Zone_t); - add_path(cgns_paths, "Base0/ZoneU2" , CGNS::Label::Zone_t); - - cgns_registry cgns_reg1 = cgns_registry(cgns_paths, test_comm); - - int g_id_zone1 = get_global_id_from_path_and_type(cgns_reg1, "Base0/ZoneU1" , CGNS::Label::Zone_t ); - - DOCTEST_MPI_CHECK(0, g_id_zone1 == 2); - - std::string z1_path = get_path_from_global_id_and_type(cgns_reg1, 2, CGNS::Label::Zone_t); - - DOCTEST_MPI_CHECK(0, z1_path == "Base0/ZoneU1" ); - - - int g_id_zone2 = get_global_id_from_path_and_type(cgns_reg1, "Base0/ZoneU2" , CGNS::Label::Zone_t ); - - DOCTEST_MPI_CHECK(0, g_id_zone2 == 1); - - std::string z2_path = get_path_from_global_id_and_type(cgns_reg1, 1, CGNS::Label::Zone_t); - - DOCTEST_MPI_CHECK(0, z2_path == "Base0/ZoneU2" ); - -} - -// --------------------------------------------------------------------------------- -MPI_TEST_CASE("[cgns_registry] Generate global numbering for zone [2 proc]",2) { - cgns_paths_by_label cgns_paths; - - int n_rank_l, i_rank_l; - MPI_Comm_size(test_comm, &n_rank_l); - MPI_Comm_rank(test_comm, &i_rank_l); - assert(n_rank_l == 2); - - if( i_rank_l == 0) { - add_path(cgns_paths, "Base0/ZoneU1" , CGNS::Label::Zone_t); - } else { - add_path(cgns_paths, "Base0/ZoneU2" , CGNS::Label::Zone_t); - } - cgns_registry cgns_reg1 = cgns_registry(cgns_paths, test_comm); - - if(i_rank_l == 0){ - - int g_id_zone1 = get_global_id_from_path_and_type(cgns_reg1, "Base0/ZoneU1" , CGNS::Label::Zone_t ); - - DOCTEST_MPI_CHECK(0, g_id_zone1 == 2); - - std::string z1_path = get_path_from_global_id_and_type(cgns_reg1, 2, CGNS::Label::Zone_t); - - DOCTEST_MPI_CHECK(0, z1_path == "Base0/ZoneU1" ); - - } else { - - int g_id_zone2 = get_global_id_from_path_and_type(cgns_reg1, "Base0/ZoneU2" , CGNS::Label::Zone_t ); - - DOCTEST_MPI_CHECK(1, g_id_zone2 == 1); - - std::string z2_path = get_path_from_global_id_and_type(cgns_reg1, 1, CGNS::Label::Zone_t); - - DOCTEST_MPI_CHECK(1, z2_path == "Base0/ZoneU2" ); - - } - -} - - -// --------------------------------------------------------------------------------- -MPI_TEST_CASE("[cgns_registry] Generate global numbering for family [1 proc]",1) { - cgns_paths_by_label cgns_paths; - - add_path(cgns_paths, "Base0/WALL" , CGNS::Label::Family_t); - add_path(cgns_paths, "Base0/FARFIELD", CGNS::Label::Family_t); - add_path(cgns_paths, "Base0/SYM" , CGNS::Label::Family_t); - add_path(cgns_paths, "Base0/WALL" , CGNS::Label::Family_t); - add_path(cgns_paths, "Base0/FARFIELD", CGNS::Label::Family_t); - add_path(cgns_paths, "Base0/SYM" , CGNS::Label::Family_t); - - cgns_registry cgns_reg1 = cgns_registry(cgns_paths, test_comm); - - int g_id_fam1 = get_global_id_from_path_and_type(cgns_reg1, "Base0/WALL" , CGNS::Label::Family_t); - int g_id_fam2 = get_global_id_from_path_and_type(cgns_reg1, "Base0/FARFIELD", CGNS::Label::Family_t); - int g_id_fam3 = get_global_id_from_path_and_type(cgns_reg1, "Base0/SYM" , CGNS::Label::Family_t); - - CHECK(g_id_fam1 == 1); - CHECK(g_id_fam2 == 2); - CHECK(g_id_fam3 == 3); - -} - -// --------------------------------------------------------------------------------- -MPI_TEST_CASE("[cgns_registry] Generate global numbering for family [2 proc]",2) { - - cgns_paths_by_label cgns_paths; - - add_path(cgns_paths, "Base0/WALL" , CGNS::Label::Family_t); - add_path(cgns_paths, "Base0/FARFIELD", CGNS::Label::Family_t); - add_path(cgns_paths, "Base0/SYM" , CGNS::Label::Family_t); - - cgns_registry cgns_reg1 = cgns_registry(cgns_paths, test_comm); - - int g_id_fam1 = get_global_id_from_path_and_type(cgns_reg1, "Base0/WALL" , CGNS::Label::Family_t); - int g_id_fam2 = get_global_id_from_path_and_type(cgns_reg1, "Base0/FARFIELD", CGNS::Label::Family_t); - int g_id_fam3 = get_global_id_from_path_and_type(cgns_reg1, "Base0/SYM" , CGNS::Label::Family_t); - - CHECK(g_id_fam1 == 1); - CHECK(g_id_fam2 == 2); - CHECK(g_id_fam3 == 3); -} diff --git a/maia/algo/part/cgns_registry/test/test_cgns_registry.py b/maia/algo/part/cgns_registry/test/test_cgns_registry.py deleted file mode 100644 index c0dd42d5..00000000 --- a/maia/algo/part/cgns_registry/test/test_cgns_registry.py +++ /dev/null @@ -1,220 +0,0 @@ -import pytest_parallel - -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns - -from cmaia.part_algo import cgns_registry as CGR -from maia.pytree.cgns_keywords import Label as CGL -from maia.algo.part.cgns_registry.tree import make_cgns_registry, add_cgns_registry_information - - -yt_1p = """ -Base0 CGNSBase_t [3,3]: - ZoneU1 Zone_t [[1331,1000,0]]: - ZoneBC ZoneBC_t: - WALL BC_t: - FARFIELD BC_t: - ZoneU2 Zone_t [[216,125,0]]: - ZoneBC ZoneBC_t: - WALL BC_t: - SYM BC_t: - WALL Family_t: - FARFIELD Family_t: - SYM Family_t: -""" - -yt_2p = [""" -Base0 CGNSBase_t [3,3]: - ZoneU1 Zone_t [[1331,1000,0]]: - ZoneBC ZoneBC_t: - WALL BC_t: - FARFIELD BC_t: - WALL Family_t: - FARFIELD Family_t: - SYM Family_t: -""", -""" -Base0 CGNSBase_t [3,3]: - ZoneU2 Zone_t [[216,125,0]]: - ZoneBC ZoneBC_t: - WALL BC_t: - SYM BC_t: - WALL Family_t: - FARFIELD Family_t: - SYM Family_t: -"""] - - -@pytest_parallel.mark.parallel(1) -def test_cgns_registry_1p(comm): - """ - """ - tree = parse_yaml_cgns.to_cgns_tree(yt_1p) - - cgr = make_cgns_registry(tree, comm) - - assert list(cgr.paths(CGL.Zone_t)) == ["Base0/ZoneU2", "Base0/ZoneU1"] - - # get_global_id_from_path_and_type - assert CGR.get_global_id_from_path_and_type(cgr, "Base0", CGL.CGNSBase_t) == 1 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1", CGL.Zone_t) == 2 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2", CGL.Zone_t) == 1 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1/ZoneBC/FARFIELD", CGL.BC_t) == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1/ZoneBC/WALL" , CGL.BC_t) == 2 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2/ZoneBC/SYM" , CGL.BC_t) == 3 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2/ZoneBC/WALL", CGL.BC_t) == 4 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/WALL" , CGL.Family_t) == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/FARFIELD", CGL.Family_t) == 2 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/SYM" , CGL.Family_t) == 3 - - # get_global_id_from_path_and_type - assert CGR.get_global_id_from_path_and_type(cgr, "Base0", "CGNSBase_t") == 1 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1", "Zone_t") == 2 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2", "Zone_t") == 1 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1/ZoneBC/FARFIELD", "BC_t") == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1/ZoneBC/WALL" , "BC_t") == 2 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2/ZoneBC/SYM" , "BC_t") == 3 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2/ZoneBC/WALL", "BC_t") == 4 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/WALL" , "Family_t") == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/FARFIELD", "Family_t") == 2 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/SYM" , "Family_t") == 3 - - # get_path_from_global_id_and_type - assert CGR.get_path_from_global_id_and_type(cgr, 1, CGL.CGNSBase_t) == "Base0" - - assert CGR.get_path_from_global_id_and_type(cgr, 2, CGL.Zone_t) == "Base0/ZoneU1" - assert CGR.get_path_from_global_id_and_type(cgr, 1, CGL.Zone_t) == "Base0/ZoneU2" - - assert CGR.get_path_from_global_id_and_type(cgr, 1, CGL.BC_t) == "Base0/ZoneU1/ZoneBC/FARFIELD" - assert CGR.get_path_from_global_id_and_type(cgr, 2, CGL.BC_t) == "Base0/ZoneU1/ZoneBC/WALL" - - assert CGR.get_path_from_global_id_and_type(cgr, 3, CGL.BC_t) == "Base0/ZoneU2/ZoneBC/SYM" - assert CGR.get_path_from_global_id_and_type(cgr, 4, CGL.BC_t) == "Base0/ZoneU2/ZoneBC/WALL" - - assert CGR.get_path_from_global_id_and_type(cgr, 1, CGL.Family_t) == "Base0/WALL" - assert CGR.get_path_from_global_id_and_type(cgr, 2, CGL.Family_t) == "Base0/FARFIELD" - assert CGR.get_path_from_global_id_and_type(cgr, 3, CGL.Family_t) == "Base0/SYM" - - # get_path_from_global_id_and_type - assert CGR.get_path_from_global_id_and_type(cgr, 1, "CGNSBase_t") == "Base0" - - assert CGR.get_path_from_global_id_and_type(cgr, 2, "Zone_t") == "Base0/ZoneU1" - assert CGR.get_path_from_global_id_and_type(cgr, 1, "Zone_t") == "Base0/ZoneU2" - - assert CGR.get_path_from_global_id_and_type(cgr, 1, "BC_t") == "Base0/ZoneU1/ZoneBC/FARFIELD" - assert CGR.get_path_from_global_id_and_type(cgr, 2, "BC_t") == "Base0/ZoneU1/ZoneBC/WALL" - - assert CGR.get_path_from_global_id_and_type(cgr, 3, "BC_t") == "Base0/ZoneU2/ZoneBC/SYM" - assert CGR.get_path_from_global_id_and_type(cgr, 4, "BC_t") == "Base0/ZoneU2/ZoneBC/WALL" - - assert CGR.get_path_from_global_id_and_type(cgr, 1, "Family_t") == "Base0/WALL" - assert CGR.get_path_from_global_id_and_type(cgr, 2, "Family_t") == "Base0/FARFIELD" - assert CGR.get_path_from_global_id_and_type(cgr, 3, "Family_t") == "Base0/SYM" - -@pytest_parallel.mark.parallel(2) -def test_cgns_registry_2p(comm): - """ - """ - tree = parse_yaml_cgns.to_cgns_tree(yt_2p[comm.Get_rank()]) - cgr = make_cgns_registry(tree, comm) - - if comm.Get_rank()==0: - assert list(cgr.paths(CGL.Zone_t)) == ["Base0/ZoneU1"] - if comm.Get_rank()==1: - assert list(cgr.paths(CGL.Zone_t)) == ["Base0/ZoneU2"] - - # get_global_id_from_path_and_type - if comm.Get_rank()==0: - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1", CGL.Zone_t) == 2 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1/ZoneBC/FARFIELD", CGL.BC_t) == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1/ZoneBC/WALL" , CGL.BC_t) == 2 - if comm.Get_rank()==1: - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2", CGL.Zone_t) == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2/ZoneBC/SYM" , CGL.BC_t) == 3 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2/ZoneBC/WALL", CGL.BC_t) == 4 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/WALL" , CGL.Family_t) == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/FARFIELD", CGL.Family_t) == 2 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/SYM" , CGL.Family_t) == 3 - - # get_global_id_from_path_and_type - assert CGR.get_global_id_from_path_and_type(cgr, "Base0", "CGNSBase_t") == 1 - - if comm.Get_rank()==0: - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1", "Zone_t") == 2 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1/ZoneBC/FARFIELD", "BC_t") == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU1/ZoneBC/WALL" , "BC_t") == 2 - if comm.Get_rank()==1: - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2", "Zone_t") == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2/ZoneBC/SYM" , "BC_t") == 3 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/ZoneU2/ZoneBC/WALL", "BC_t") == 4 - - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/WALL" , "Family_t") == 1 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/FARFIELD", "Family_t") == 2 - assert CGR.get_global_id_from_path_and_type(cgr, "Base0/SYM" , "Family_t") == 3 - - # get_path_from_global_id_and_type - assert CGR.get_path_from_global_id_and_type(cgr, 1, CGL.CGNSBase_t) == "Base0" - - if comm.Get_rank()==0: - assert CGR.get_path_from_global_id_and_type(cgr, 2, CGL.Zone_t) == "Base0/ZoneU1" - assert CGR.get_path_from_global_id_and_type(cgr, 1, CGL.BC_t) == "Base0/ZoneU1/ZoneBC/FARFIELD" - assert CGR.get_path_from_global_id_and_type(cgr, 2, CGL.BC_t) == "Base0/ZoneU1/ZoneBC/WALL" - if comm.Get_rank()==1: - assert CGR.get_path_from_global_id_and_type(cgr, 1, CGL.Zone_t) == "Base0/ZoneU2" - assert CGR.get_path_from_global_id_and_type(cgr, 3, CGL.BC_t) == "Base0/ZoneU2/ZoneBC/SYM" - assert CGR.get_path_from_global_id_and_type(cgr, 4, CGL.BC_t) == "Base0/ZoneU2/ZoneBC/WALL" - - assert CGR.get_path_from_global_id_and_type(cgr, 1, CGL.Family_t) == "Base0/WALL" - assert CGR.get_path_from_global_id_and_type(cgr, 2, CGL.Family_t) == "Base0/FARFIELD" - assert CGR.get_path_from_global_id_and_type(cgr, 3, CGL.Family_t) == "Base0/SYM" - - # get_path_from_global_id_and_type - assert CGR.get_path_from_global_id_and_type(cgr, 1, "CGNSBase_t") == "Base0" - - if comm.Get_rank()==0: - assert CGR.get_path_from_global_id_and_type(cgr, 2, "Zone_t") == "Base0/ZoneU1" - assert CGR.get_path_from_global_id_and_type(cgr, 1, "BC_t") == "Base0/ZoneU1/ZoneBC/FARFIELD" - assert CGR.get_path_from_global_id_and_type(cgr, 2, "BC_t") == "Base0/ZoneU1/ZoneBC/WALL" - if comm.Get_rank()==1: - assert CGR.get_path_from_global_id_and_type(cgr, 1, "Zone_t") == "Base0/ZoneU2" - assert CGR.get_path_from_global_id_and_type(cgr, 3, "BC_t") == "Base0/ZoneU2/ZoneBC/SYM" - assert CGR.get_path_from_global_id_and_type(cgr, 4, "BC_t") == "Base0/ZoneU2/ZoneBC/WALL" - - assert CGR.get_path_from_global_id_and_type(cgr, 1, "Family_t") == "Base0/WALL" - assert CGR.get_path_from_global_id_and_type(cgr, 2, "Family_t") == "Base0/FARFIELD" - assert CGR.get_path_from_global_id_and_type(cgr, 3, "Family_t") == "Base0/SYM" - -@pytest_parallel.mark.parallel(1) -def test_add_cgns_registry_information_1p(comm): - """ - """ - tree = parse_yaml_cgns.to_cgns_tree(yt_1p) - cgr = add_cgns_registry_information(tree, comm) - - zone1_id_n = PT.get_node_from_path(tree, "Base0/ZoneU1/:CGNS#Registry") - zone2_id_n = PT.get_node_from_path(tree, "Base0/ZoneU2/:CGNS#Registry") - - assert PT.get_value(zone1_id_n) == 2 - assert PT.get_value(zone2_id_n) == 1 - -@pytest_parallel.mark.parallel(2) -def test_add_cgns_registry_information_2p(comm): - """ - """ - tree = parse_yaml_cgns.to_cgns_tree(yt_2p[comm.Get_rank()]) - cgr = add_cgns_registry_information(tree, comm) - - if comm.Get_rank()==0: - assert PT.get_value(PT.get_node_from_path(tree, "Base0/ZoneU1/:CGNS#Registry")) == 2 - if comm.Get_rank()==1: - assert PT.get_value(PT.get_node_from_path(tree, "Base0/ZoneU2/:CGNS#Registry")) == 1 - diff --git a/maia/algo/part/cgns_registry/tree.py b/maia/algo/part/cgns_registry/tree.py deleted file mode 100644 index c9de7ce7..00000000 --- a/maia/algo/part/cgns_registry/tree.py +++ /dev/null @@ -1,122 +0,0 @@ -from mpi4py import MPI - -import maia.pytree as PT - -from cmaia.part_algo import cgns_registry as CGR -import maia.pytree.cgns_keywords as CGK -CGL = CGK.Label - -def build_paths_by_label_bcdataset(paths_by_label, bc, bc_path): - """ - Factorize BC+GC - """ - for bcds in PT.get_children_from_label(bc, 'BCDataSet_t'): - bcds_path = bc_path+"/"+PT.get_name(bcds) - CGR.add_path(paths_by_label, bcds_path, "BCDataSet_t") - for bcd in PT.get_children_from_label(bcds, 'BCData_t'): - bcd_path = bcds_path+"/"+PT.get_name(bcd) - CGR.add_path(paths_by_label, bcd_path, "BCData_t") - -def build_paths_by_label_zone(paths_by_label, zone, zone_path): - """ - """ - zone_bc = PT.get_node_from_label(zone, CGL.ZoneBC_t) - if zone_bc is not None: - zone_bc_path = f"{zone_path}/{PT.get_name(zone_bc)}" - CGR.add_path(paths_by_label, zone_bc_path, CGL.ZoneBC_t.name) - for bc in PT.get_children_from_label(zone_bc, 'BC_t'): - bc_path = f"{zone_bc_path}/{PT.get_name(bc)}" - CGR.add_path(paths_by_label, bc_path, "BC_t") - build_paths_by_label_bcdataset(paths_by_label, bc, bc_path) - - for zone_gc in PT.iter_children_from_label(zone, CGL.ZoneGridConnectivity_t): - zone_gc_path = f"{zone_path}/{PT.get_name(zone_gc)}" - CGR.add_path(paths_by_label, zone_gc_path, CGL.ZoneGridConnectivity_t.name) - - for gc in PT.iter_children_from_label(zone_gc, CGL.GridConnectivity_t): - gc_path = f"{zone_gc_path}/{PT.get_name(gc)}" - CGR.add_path(paths_by_label, gc_path, CGL.GridConnectivity_t.name) - build_paths_by_label_bcdataset(paths_by_label, gc, gc_path) - - for gc1to1 in PT.iter_children_from_label(zone_gc, CGL.GridConnectivity1to1_t): - gc1to1_path = f"{zone_gc_path}/{PT.get_name(gc1to1)}" - # > Here is a little hack to have gridConnectivity in same label - CGR.add_path(paths_by_label, gc1to1_path, CGL.GridConnectivity_t.name) - build_paths_by_label_bcdataset(paths_by_label, gc1to1, gc1to1_path) - -def build_paths_by_label_family(paths_by_label, parent, parent_path): - """ - """ - for family in PT.iter_children_from_label(parent, CGL.Family_t): - if family != parent: - family_path = f"{parent_path}/{PT.get_name(family)}" - CGR.add_path(paths_by_label, family_path, CGL.Family_t.name) - - family_bc = PT.get_node_from_label(family, CGL.FamilyBC_t, depth=1) - if family_bc is not None: - family_bc_path = f"{family_path}/{PT.get_name(family_bc)}" - CGR.add_path(paths_by_label, family_bc_path, CGL.FamilyBC_t.name) - - for family_bcdataset in PT.iter_children_from_label(family_bc, CGL.FamilyBCDataSet_t): - family_bcdataset_path = f"{family_bc_path}/{PT.get_name(family_bcdataset)}" - CGR.add_path(paths_by_label, family_bcdataset_path, CGL.FamilyBCDataSet_t.name) - build_paths_by_label_bcdataset(paths_by_label, family_bcdataset, family_bcdataset_path) - - # Hierarchic family - build_paths_by_label_family(paths_by_label, family, family_path) - -def setup_child_from_type(paths_by_label, parent, parent_path, cgns_type): - """ - """ - for child in PT.iter_children_from_label(parent, cgns_type): - child_path = parent_path+"/"+PT.get_name(child) - CGR.add_path(paths_by_label, child_path, cgns_type) - -def build_paths_by_label(tree): - """ - """ - paths_by_label = CGR.cgns_paths_by_label(); - - for base in PT.get_all_CGNSBase_t(tree): - base_path = PT.get_name(base) - CGR.add_path(paths_by_label, base_path, u'CGNSBase_t') - - setup_child_from_type(paths_by_label, base, base_path, 'FlowEquationSet_t') - setup_child_from_type(paths_by_label, base, base_path, 'ViscosityModel_t') - - build_paths_by_label_family(paths_by_label, base, base_path) - - for zone in PT.iter_nodes_from_label(base, CGL.Zone_t): - zone_path = f"{base_path}/{PT.get_name(zone)}" - CGR.add_path(paths_by_label, zone_path, CGL.Zone_t.name) - build_paths_by_label_zone(paths_by_label, zone, zone_path) - - return paths_by_label - -def make_cgns_registry(tree, comm): - """ - Generate for each nodes a global identifier - """ - paths_by_label = build_paths_by_label(tree) - cgr = CGR.cgns_registry(paths_by_label, comm) - return cgr - - -def add_cgns_registry_information(tree, comm): - """ - """ - cgr = make_cgns_registry(tree, comm) - - for itype in range(CGK.nb_cgns_labels): - paths = cgr.paths(itype) - global_ids = cgr.global_ids(itype) - for i in range(len(paths)): - node = PT.get_node_from_path(tree, paths[i]) - cgns_registry_n = PT.get_node_from_name_and_label(node, ":CGNS#Registry", 'UserDefined_t') - # Looks strange - if cgns_registry_n: - PT.rm_nodes_from_name_and_label(node, ":CGNS#Registry", "UserDefined_t") - else: - PT.new_node(name=":CGNS#Registry", value=global_ids[i], label='UserDefined_t', parent=node) - - return cgr diff --git a/maia/algo/part/closest_points.py b/maia/algo/part/closest_points.py deleted file mode 100644 index f1cfbc62..00000000 --- a/maia/algo/part/closest_points.py +++ /dev/null @@ -1,112 +0,0 @@ -import numpy as np - -import Pypdm.Pypdm as PDM - -import maia.pytree as PT - -from maia.utils import py_utils, np_utils -from maia.factory.dist_from_part import get_parts_per_blocks - -from .point_cloud_utils import get_shifted_point_clouds - - -def _closest_points(src_clouds, tgt_clouds, comm, n_pts=1, reverse=False): - """ Wrapper of PDM mesh location - For now, only 1 domain is supported so we expect source parts and target clouds - as flat lists of tuples (coords, lngn) - """ - - # > Create and setup global data - closest_point = PDM.ClosestPoints(comm, n_closest=n_pts) - closest_point.n_part_cloud_set(len(src_clouds), len(tgt_clouds)) - - # > Setup source - for i_part, (coords, lngn) in enumerate(src_clouds): - closest_point.src_cloud_set(i_part, lngn.shape[0], coords, lngn) - - # > Setup target - for i_part, (coords, lngn) in enumerate(tgt_clouds): - closest_point.tgt_cloud_set(i_part, lngn.shape[0], coords, lngn) - - closest_point.compute() - - all_closest = [closest_point.points_get(i_part_tgt) for i_part_tgt in range(len(tgt_clouds))] - - if reverse: - all_closest_inv = [closest_point.tgt_in_src_get(i_src_part) for i_src_part in range(len(src_clouds))] - return all_closest, all_closest_inv - else: - return all_closest - -def _find_closest_points(src_parts_per_dom, tgt_parts_per_dom, src_location, tgt_location, comm, reverse=False): - n_dom_src = len(src_parts_per_dom) - n_dom_tgt = len(tgt_parts_per_dom) - - n_part_per_dom_src = [len(parts) for parts in src_parts_per_dom] - n_part_per_dom_tgt = [len(parts) for parts in tgt_parts_per_dom] - n_part_src = sum(n_part_per_dom_src) - n_part_tgt = sum(n_part_per_dom_tgt) - - # > Setup source - src_offset, src_clouds = get_shifted_point_clouds(src_parts_per_dom, src_location, comm) - src_clouds = py_utils.to_flat_list(src_clouds) - - # > Setup target - tgt_offset, tgt_clouds = get_shifted_point_clouds(tgt_parts_per_dom, tgt_location, comm) - tgt_clouds = py_utils.to_flat_list(tgt_clouds) - - result = _closest_points(src_clouds, tgt_clouds, comm, 1, reverse) - - # Shift back result - direct_result = result[0] if reverse else result - for tgt_result in direct_result: - gnum_shifted = tgt_result.pop('closest_src_gnum') - tgt_result['closest_src_gnum'], tgt_result['domain'] = np_utils.shifted_to_local(gnum_shifted, src_offset) - if reverse: - for src_result in result[1]: - gnum_shifted = src_result.pop('tgt_in_src') - src_result['tgt_in_src'], src_result['domain'] = np_utils.shifted_to_local(gnum_shifted, tgt_offset) - # Reshape output to list of lists (as input domains) - if reverse: - return py_utils.to_nested_list(result[0], n_part_per_dom_tgt),\ - py_utils.to_nested_list(result[1], n_part_per_dom_src) - else: - return py_utils.to_nested_list(result, n_part_per_dom_tgt) - -def find_closest_points(src_tree, tgt_tree, location, comm): - """Find the closest points between two partitioned trees. - - For all points of the target tree matching the given location, - search the closest point of same location in the source tree. - The result, i.e. the gnum & domain number of the source point, are stored in a ``DiscreteData_t`` - container called "ClosestPoint" on the target zones. - The ids of source points refers to cells or vertices depending on the chosen location. - - Args: - src_tree (CGNSTree): Source tree, partitionned - tgt_tree (CGNSTree): Target tree, partitionned - location ({'CellCenter', 'Vertex'}) : Entity to use to compute closest points - comm (MPIComm): MPI communicator - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #find_closest_points@start - :end-before: #find_closest_points@end - :dedent: 2 - """ - _src_parts_per_dom = get_parts_per_blocks(src_tree, comm) - src_parts_per_dom = list(_src_parts_per_dom.values()) - tgt_parts_per_dom = list(get_parts_per_blocks(tgt_tree, comm).values()) - - closest_data = _find_closest_points(src_parts_per_dom, tgt_parts_per_dom, location, location, comm) - - dom_list = '\n'.join(_src_parts_per_dom.keys()) - for i_dom, tgt_parts in enumerate(tgt_parts_per_dom): - for i_part, tgt_part in enumerate(tgt_parts): - shape = PT.Zone.CellSize(tgt_part) if location == 'CellCenter' else PT.Zone.VertexSize(tgt_part) - data = closest_data[i_dom][i_part] - sol = PT.update_child(tgt_part, "ClosestPoint", "DiscreteData_t") - PT.new_GridLocation(location, sol) - PT.new_DataArray("SrcId", data['closest_src_gnum'].reshape(shape, order='F'), parent=sol) - PT.new_DataArray("DomId", data['domain'].reshape(shape, order='F'), parent=sol) - PT.new_node("DomainList", "Descriptor_t", dom_list, parent=sol) diff --git a/maia/algo/part/connectivity_transform.py b/maia/algo/part/connectivity_transform.py deleted file mode 100644 index 0ee1a098..00000000 --- a/maia/algo/part/connectivity_transform.py +++ /dev/null @@ -1,44 +0,0 @@ -import numpy as np - -import maia.pytree as PT -from cmaia.part_algo import enforce_pe_left_parent - -def enforce_boundary_pe_left(zone_node): - """ - Force the boundary ngon to have a non zero left parent cell. - In such case, connetivities (FaceVtx & NFace) are reversed to preserve face - orientation - """ - if not PT.Zone.has_ngon_elements(zone_node): #Early return if zone is Elts defined - return - - ngon = PT.Zone.NGonNode(zone_node) - z_dim = 2 - if PT.Zone.has_nface_elements(zone_node) or PT.get_child_from_name(ngon, "ParentElements") is not None: - z_dim = 3 - - if z_dim == 3: - try: - nface = PT.Zone.NFaceNode(zone_node) - enforce_pe_left_parent(PT.get_child_from_name(ngon, 'ElementStartOffset')[1], - PT.get_child_from_name(ngon, 'ElementConnectivity')[1], - PT.get_child_from_name(ngon, 'ParentElements')[1], - PT.get_child_from_name(nface, 'ElementStartOffset')[1], - PT.get_child_from_name(nface, 'ElementConnectivity')[1]) - except RuntimeError: #3D, but no NFace - enforce_pe_left_parent(PT.get_child_from_name(ngon, 'ElementStartOffset')[1], - PT.get_child_from_name(ngon, 'ElementConnectivity')[1], - PT.get_child_from_name(ngon, 'ParentElements')[1]) - - elif z_dim == 2: - bar_elts = [e for e in PT.iter_children_from_label(zone_node, 'Elements_t') if PT.Element.CGNSName(e) == 'BAR_2'] - if len(bar_elts) > 1: - raise RuntimeError("Multiple BAR elements are not managed") - elif len(bar_elts) == 1: - nedge = bar_elts[0] - nedge_eso = 2*np.arange(PT.Element.Size(nedge)+1, dtype=np.int32) - enforce_pe_left_parent(nedge_eso, - PT.get_child_from_name(nedge, 'ElementConnectivity')[1], - PT.get_child_from_name(nedge, 'ParentElements')[1]) - - diff --git a/maia/algo/part/connectivity_utils.py b/maia/algo/part/connectivity_utils.py deleted file mode 100644 index 3dbed7e3..00000000 --- a/maia/algo/part/connectivity_utils.py +++ /dev/null @@ -1,93 +0,0 @@ -import numpy as np - -import maia.pytree as PT - -from maia.utils import np_utils -from maia.algo.part.ngon_tools import pe_to_nface -from maia.utils import s_numbering - -import Pypdm.Pypdm as PDM - -def cell_vtx_connectivity_S(zone_S, dim) : - n_cell = PT.Zone.n_cell(zone_S) - vertex_size = PT.Zone.VertexSize(zone_S) - - if dim == 2: - cell_vtx_idx = 4*np.arange(0, n_cell+1, dtype=np.int32) - cell_vtx = np.zeros(4*n_cell, dtype=np.int32) - i = np.arange(1, vertex_size[0]) - j = np.arange(1, vertex_size[1]).reshape(-1,1) - cell_vtx[0::4] = s_numbering.ijk_to_index(i, j, 1, vertex_size).flatten() - cell_vtx[1::4] = s_numbering.ijk_to_index(i+1, j, 1, vertex_size).flatten() - cell_vtx[2::4] = s_numbering.ijk_to_index(i+1, j+1, 1, vertex_size).flatten() - cell_vtx[3::4] = s_numbering.ijk_to_index(i, j+1, 1, vertex_size).flatten() - elif dim == 3: - cell_vtx_idx = 8*np.arange(0, n_cell+1, dtype=np.int32) - cell_vtx = np.zeros(8*n_cell, dtype=np.int32) - i = np.arange(1, vertex_size[0]) - j = np.arange(1, vertex_size[1]).reshape(-1,1) - k = np.arange(1, vertex_size[2]).reshape(-1,1,1) - cell_vtx[0::8] = s_numbering.ijk_to_index(i, j, k, vertex_size).flatten() - cell_vtx[1::8] = s_numbering.ijk_to_index(i+1, j, k, vertex_size).flatten() - cell_vtx[2::8] = s_numbering.ijk_to_index(i+1, j+1, k, vertex_size).flatten() - cell_vtx[3::8] = s_numbering.ijk_to_index(i, j+1, k, vertex_size).flatten() - cell_vtx[4::8] = s_numbering.ijk_to_index(i, j, k+1, vertex_size).flatten() - cell_vtx[5::8] = s_numbering.ijk_to_index(i+1, j, k+1, vertex_size).flatten() - cell_vtx[6::8] = s_numbering.ijk_to_index(i+1, j+1, k+1, vertex_size).flatten() - cell_vtx[7::8] = s_numbering.ijk_to_index(i, j+1, k+1, vertex_size).flatten() - else: - raise NotImplementedError("Unsupported dimension") - - return cell_vtx_idx, cell_vtx - -def cell_vtx_connectivity_ngon(zone, dim): - if dim==1: - raise NotImplementedError("U-NGON meshes doesn't support dimension 1 elements") - - ngon_node = PT.Zone.NGonNode(zone) - ngon_eso = PT.get_child_from_name(ngon_node, 'ElementStartOffset')[1] - ngon_ec = PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1] - - if dim==2: - return ngon_eso, ngon_ec - - if not PT.Zone.has_nface_elements(zone): - pe_to_nface(zone) - - nface_node = PT.Zone.NFaceNode(zone) - nface_eso = PT.get_child_from_name(nface_node, 'ElementStartOffset')[1] - nface_ec = PT.get_child_from_name(nface_node, 'ElementConnectivity')[1] - - return PDM.combine_connectivity(nface_eso, nface_ec, ngon_eso, ngon_ec) - -def cell_vtx_connectivity_elts(zone, dim): - ordered_elts = PT.Zone.get_ordered_elements_per_dim(zone) - connectivities = [PT.get_child_from_name(e, 'ElementConnectivity')[1] for e in ordered_elts[dim]] - n_elts = sum([PT.Element.Size(e) for e in ordered_elts[dim]]) - _, cell_vtx = np_utils.concatenate_np_arrays(connectivities, dtype=np.int32) - cell_vtx_idx = np.empty(n_elts+1, np.int32) - cell_vtx_idx[0] = 0 - - cur = 1 - for elt in ordered_elts[dim]: - cell_vtx_idx[cur:cur+PT.Element.Size(elt)] = \ - PT.Element.NVtx(elt) * np.arange(1, PT.Element.Size(elt)+1, dtype=np.int32) + cell_vtx_idx[cur-1] - cur += PT.Element.Size(elt) - assert cur == n_elts +1 - - return cell_vtx_idx, cell_vtx - -def cell_vtx_connectivity(zone, dim=3): - """ - Compute and return the cell->vtx connectivity on a partitioned zone - """ - assert dim in [1,2,3] - assert PT.Zone.Type(zone) in ['Structured', 'Unstructured'] - - if PT.Zone.Type(zone) == 'Structured': - return cell_vtx_connectivity_S(zone, dim) - else: - if PT.Zone.has_ngon_elements(zone): - return cell_vtx_connectivity_ngon(zone, dim) - else: # zone has standard elements - return cell_vtx_connectivity_elts(zone, dim) diff --git a/maia/algo/part/extract_boundary.py b/maia/algo/part/extract_boundary.py deleted file mode 100644 index e4eefcfd..00000000 --- a/maia/algo/part/extract_boundary.py +++ /dev/null @@ -1,156 +0,0 @@ -import numpy as np - -import maia.pytree as PT - -from maia.utils import np_utils, s_numbering, pr_utils -from maia.transfer import utils as te_utils - -from .point_cloud_utils import create_sub_numbering - -def _pr_to_face_pl(n_vtx_zone, pr, input_loc): - """ - Transform a (partitioned) PointRange pr of any location input_loc into a PointList - supported by the faces. n_vtx_zone is the number of vertices of the zone to which the - pr belongs. Output face are numbered using s_numb conventions (i faces, then j faces, then - k faces in increasing i,j,k for each group) - """ - - bnd_axis = PT.Subset.normal_axis(PT.new_BC(point_range=pr, loc=input_loc)) - - # It is safer to reuse slabs to manage all cases (eg input location or reversed pr) - bc_size = pr_utils.transform_bnd_pr_size(pr, input_loc, "FaceCenter") - - slab = np.empty((3,2), order='F', dtype=np.int32) - slab[:,0] = pr[:,0] - slab[:,1] = bc_size + pr[:,0] - 1 - slab[bnd_axis,:] += pr_utils.normal_index_shift(pr, n_vtx_zone, bnd_axis, input_loc, "FaceCenter") - - return pr_utils.compute_pointList_from_pointRanges([slab], n_vtx_zone, ['I', 'J', 'K'][bnd_axis]+'FaceCenter') - -def _extract_sub_connectivity(array_idx, array, sub_elts): - """ - From an idx+array mother->child connectivity (eg face->vtx or cell->face) and a list of - mother element ids (starting at 1), create a sub connectivity involving only these mothers. - Return the new idx+array, where child element are renumbered from 1 to nb (unique) childs, without - hole. In addition, return the childs_ids array containing the new_to_old indirection for child ids. - """ - starts = array_idx[sub_elts - 1] - ends = array_idx[sub_elts - 1 + 1] - - sub_array_idx = np_utils.sizes_to_indices(ends - starts) - #This is the sub connectivity (only for sub_elts), but in old numbering - sub_face_vtx = array[np_utils.multi_arange(starts, ends)] - - #Get the udpated connectivity with local numbering - child_ids, sub_array = np.unique(sub_face_vtx, return_inverse=True) - sub_array = sub_array.astype(array.dtype) + 1 - - return sub_array_idx, sub_array, child_ids - - -def extract_faces_mesh(zone, face_ids): - """ - Extract a sub mesh from a U or S zone and a (flat) list of face ids to extract : - create the sub ngon connectivity and extract the coordinates of vertices - belonging to the sub mesh. - For S zone, faces to extract must be converted from i,j,k to index before processing - """ - # NGon Extraction - if PT.Zone.Type(zone) == 'Unstructured': - if PT.Zone.has_ngon_elements(zone): - face_vtx_idx, face_vtx, _ = PT.Zone.ngon_connectivity(zone) - else: # Zone has std elements - sections_2d = PT.Zone.get_ordered_elements_per_dim(zone)[2] - elem_size_list = [PT.Element.Size(elt) for elt in sections_2d] - face_n_vtx_list = [PT.Element.NVtx(elt) for elt in sections_2d] - _, face_vtx = np_utils.concatenate_np_arrays([PT.get_node_from_name(elt, 'ElementConnectivity')[1] for elt in sections_2d]) - face_vtx_idx = np_utils.sizes_to_indices(np.repeat(face_n_vtx_list, elem_size_list), dtype=np.int32) - elif PT.Zone.Type(zone) == 'Structured': - # For S zone, create a NGon connectivity - n_vtx_zone = PT.Zone.VertexSize(zone) - nf_i, nf_j, nf_k = PT.Zone.FaceSize(zone) - n_face_tot = nf_i + nf_j + nf_k - - bounds = np.array([0, nf_i, nf_i + nf_j, nf_i + nf_j + nf_k], np.int32) - - face_vtx_idx = 4*np.arange(0, n_face_tot+1, dtype=np.int32) - face_vtx, _ = s_numbering.ngon_dconnectivity_from_gnum(bounds+1, n_vtx_zone-1, dtype=np.int32) - - ex_face_vtx_idx, ex_face_vtx, vtx_ids = _extract_sub_connectivity(face_vtx_idx, face_vtx, face_ids) - - # Vertex extraction - cx, cy, cz = PT.Zone.coordinates(zone) - if PT.Zone.Type(zone) == 'Unstructured': - ex_cx = cx[vtx_ids-1] - ex_cy = cy[vtx_ids-1] - ex_cz = cz[vtx_ids-1] - elif PT.Zone.Type(zone) == 'Structured': - i_idx, j_idx, k_idx = s_numbering.index_to_ijk(vtx_ids, n_vtx_zone) - ex_cx = cx[i_idx-1, j_idx-1, k_idx-1].flatten() - ex_cy = cy[i_idx-1, j_idx-1, k_idx-1].flatten() - ex_cz = cz[i_idx-1, j_idx-1, k_idx-1].flatten() - - return ex_cx, ex_cy, ex_cz, ex_face_vtx_idx, ex_face_vtx, vtx_ids - - -def extract_surf_from_bc(part_zones, bc_predicate, comm): - """ - From a list of partitioned zones (coming from the same initial domain), get the list - of faces belonging to any bc satisfiyng bc_predicate and extract the surfacic mesh. - In addition, compute a new global numbering (over the procs and the part_zones) of the extracted - faces and vertex (starting a 1 without gap) - - Return lists (of size n_part) of sub face_vtx connectivity, sub vtx coordinates and global numberings - """ - - bc_face_vtx_l = [] - bc_face_vtx_idx_l = [] - bc_coords_l = [] - parent_face_lngn_l = [] - parent_vtx_lngn_l = [] - for zone in part_zones: - is_relevant_bc = lambda n: PT.get_label(n) == 'BC_t' and bc_predicate(n) - if PT.Zone.Type(zone) == 'Unstructured': - bc_nodes = PT.get_children_from_predicates(zone, ['ZoneBC_t', lambda n: is_relevant_bc(n) and PT.Subset.GridLocation(n) == 'FaceCenter']) - bc_face_ids = [PT.get_child_from_name(bc_node, 'PointList')[1][0] for bc_node in bc_nodes] - else: - n_vtx_z = PT.Zone.VertexSize(zone) - bc_nodes = PT.get_children_from_predicates(zone, ['ZoneBC_t', is_relevant_bc]) - bc_face_ids = [_pr_to_face_pl(n_vtx_z, PT.get_child_from_name(bc_node, 'PointRange')[1], PT.Subset.GridLocation(bc_node))[0] \ - for bc_node in bc_nodes] - - _, bc_face_ids = np_utils.concatenate_np_arrays(bc_face_ids, np.int32) - # If zone is by elements, we must shift the bc_face_ids to make it start a 1 - if PT.Zone.Type(zone) == 'Unstructured' and not PT.Zone.has_ngon_elements(zone): - ordering = PT.Zone.elt_ordering_by_dim(zone) - if ordering == 1: #Increasing elements : substract starting point of 2D - bc_face_ids -= (PT.Zone.get_elt_range_per_dim(zone)[2][0] - 1) - elif ordering == -1: #Decreasing elements : substract number of 3D - bc_face_ids -= PT.Zone.get_elt_range_per_dim(zone)[3][1] - else: - raise RuntimeError("Unable to extract unordered faces") - - cx, cy, cz, bc_face_vtx_idx, bc_face_vtx, bc_vtx_ids = extract_faces_mesh(zone, bc_face_ids) - - ex_coords = np_utils.interweave_arrays([cx, cy, cz]) - bc_coords_l.append(ex_coords) - bc_face_vtx_l.append(bc_face_vtx) - bc_face_vtx_idx_l.append(bc_face_vtx_idx) - - vtx_ln_to_gn_zone = PT.maia.getGlobalNumbering(zone, 'Vertex')[1] - - if PT.Zone.Type(zone) == 'Unstructured' and not PT.Zone.has_ngon_elements(zone): - face_ln_to_gn_zone = np.concatenate([PT.maia.getGlobalNumbering(elt, "Sections")[1] \ - for elt in PT.Zone.get_ordered_elements_per_dim(zone)[2]]) - else: - _, _, face_ln_to_gn_zone, _ = te_utils.get_entities_numbering(zone) # !! Only S or NGON - - parent_face_lngn_l.append(face_ln_to_gn_zone[bc_face_ids-1]) - parent_vtx_lngn_l .append(vtx_ln_to_gn_zone[bc_vtx_ids-1] ) - - # Compute extracted gnum from parents - bc_face_lngn_l = create_sub_numbering(parent_face_lngn_l, comm) - bc_vtx_lngn_l = create_sub_numbering(parent_vtx_lngn_l, comm) - - return bc_face_vtx_l, bc_face_vtx_idx_l, bc_face_lngn_l, bc_coords_l, bc_vtx_lngn_l - diff --git a/maia/algo/part/extract_part.py b/maia/algo/part/extract_part.py deleted file mode 100644 index 5a4b096f..00000000 --- a/maia/algo/part/extract_part.py +++ /dev/null @@ -1,385 +0,0 @@ -import time -import mpi4py.MPI as MPI - -import maia -import maia.pytree as PT -import maia.utils.logging as mlog -from maia.factory import dist_from_part -from maia.utils import np_utils -from .extract_part_s import exchange_field_s, extract_part_one_domain_s -from .extract_part_u import exchange_field_u, extract_part_one_domain_u -from .extraction_utils import LOC_TO_DIM - -import numpy as np - -import Pypdm.Pypdm as PDM - - -def set_transfer_dataset(bc_n, zsr_bc_n, zone_type): - - if zone_type=='Structured': - unwanted_type = 'IndexArray_t' - required_name = 'PointRange' - else: - unwanted_type = 'IndexRange_t' - required_name = 'PointList' - there_is_dataset = False - assert PT.get_child_from_predicates(bc_n, f'BCDataSet_t/{unwanted_type}') is None,\ - 'BCDataSet_t with PointList aren\'t managed' - ds_arrays = PT.get_children_from_predicates(bc_n, 'BCDataSet_t/BCData_t/DataArray_t') - for ds_array in ds_arrays: - PT.new_DataArray(name=PT.get_name(ds_array), value=PT.get_value(ds_array), parent=zsr_bc_n) - if len(ds_arrays) != 0: - there_is_dataset = True - # PL and Location is needed for data exchange, but this should be done in ZSR func - for name in [required_name, 'GridLocation']: - PT.add_child(zsr_bc_n, PT.get_child_from_name(bc_n, name)) - return there_is_dataset - -class Extractor: - def __init__( self, - part_tree, patch, location, comm, - # equilibrate=True, - graph_part_tool="hilbert"): - - self.part_tree = part_tree - self.exch_tool_box = dict() - self.comm = comm - - # Get zones by domains - part_tree_per_dom = dist_from_part.get_parts_per_blocks(part_tree, comm) - # Check : monodomain - assert len(part_tree_per_dom.values()) == 1 - - # > Check if U or S (working because monodomain) - zone_type = PT.get_node_from_name(part_tree, 'ZoneType') - is_struct = PT.get_value(zone_type)=='Structured' if zone_type is not None else False - self.is_struct = comm.allreduce(is_struct) - # if self.is_struct and equilibrate: - # raise NotImplementedError('Structured Extractor with equilibrate=True option is not yet implemented.') - # if not self.is_struct and not equilibrate: - # raise NotImplementedError('Unstructured Extractor with equilibrate=False option is not yet implemented.') - - # ExtractPart dimension - self.location = location - self.dim = LOC_TO_DIM[location] - assert self.dim in [0,2,3], "[MAIA] Error : dimensions 1 not yet implemented" - #CGNS does not support 0D, so keep input dim in this case (which is 3 since 2d is not managed) - if location == 'Vertex': - if self.is_struct: - cell_dim = -1 - for domain_prs in patch: - for part_pr in domain_prs: - if part_pr.size!=0: - size_per_dim = np.diff(part_pr)[:,0] - idx = np.where(size_per_dim!=0)[0] - cell_dim = idx.size - cell_dim = comm.allreduce(cell_dim, op=MPI.MAX) - else: - cell_dim = 3 - else: - cell_dim = self.dim - - assert graph_part_tool in ["hilbert","parmetis","ptscotch"] - assert not( (self.dim==0) and graph_part_tool in ['parmetis', 'ptscotch']),\ - '[MAIA] Vertex extraction not available with parmetis or ptscotch partitioning. Please check your script.' - - # ExtractPart CGNSTree - extract_tree = PT.new_CGNSTree() - extract_base = PT.new_CGNSBase('Base', cell_dim=cell_dim, phy_dim=3, parent=extract_tree) - # Compute extract part of each domain - for i_domain, dom_part_zones in enumerate(part_tree_per_dom.items()): - dom_path = dom_part_zones[0] - part_zones = dom_part_zones[1] - if self.is_struct: - extract_zones, etb = extract_part_one_domain_s(part_zones, patch[i_domain], self.location, comm) - else: - extract_zones, etb = extract_part_one_domain_u(part_zones, patch[i_domain], self.location, comm, - # equilibrate=equilibrate, - graph_part_tool=graph_part_tool) - self.exch_tool_box[dom_path] = etb - for extract_zone in extract_zones: - if PT.Zone.n_vtx(extract_zone)!=0: - PT.add_child(extract_base, extract_zone) - - # > Clean orphan GC - if self.is_struct: - all_zone_name_l = PT.get_names(PT.get_children_from_label(extract_base, 'Zone_t')) - all_zone_name_l = comm.allgather(all_zone_name_l) - all_zone_name = list(np.concatenate(all_zone_name_l)) - - for zone_n in PT.get_children_from_label(extract_base, 'Zone_t'): - for zgc_n in PT.get_children_from_label(zone_n, 'ZoneGridConnectivity_t'): - for gc_n in PT.get_children_from_label(zgc_n, 'GridConnectivity1to1_t'): - matching_zone_name = PT.get_value(gc_n) - if matching_zone_name not in all_zone_name: - PT.rm_child(zgc_n, gc_n) - if len(PT.get_children_from_label(zgc_n, 'GridConnectivity1to1_t'))==0: - PT.rm_child(zone_n, zgc_n) - - self.extract_tree = extract_tree - - def exchange_fields(self, fs_container): - exchange_fld_func = exchange_field_s if self.is_struct else exchange_field_u - exchange_fld_func(self.part_tree, self.extract_tree , self.dim, self.exch_tool_box,\ - fs_container, self.comm) - - def get_extract_part_tree(self) : - return self.extract_tree - - -def extract_part_from_zsr(part_tree, zsr_name, comm, - transfer_dataset=True, - containers_name=[], **options): - """Extract the submesh defined by the provided ZoneSubRegion from the input volumic - partitioned tree. - - Dimension of the output mesh is set up accordingly to the GridLocation of the ZoneSubRegion. - Submesh is returned as an independant partitioned CGNSTree and includes the relevant connectivities. - - Fields found under the ZSR node are transfered to the extracted mesh if ``transfer_dataset`` is set to True. - In addition, additional containers specified in ``containers_name`` list are transfered to the extracted tree. - Containers to be transfered can be either of label FlowSolution_t or ZoneSubRegion_t. - - Args: - part_tree (CGNSTree) : Partitioned tree from which extraction is computed. U-Elts - connectivities are *not* managed. - zsr_name (str) : Name of the ZoneSubRegion_t node - comm (MPIComm) : MPI communicator - transfer_dataset(bool) : Transfer (or not) fields stored in ZSR to the extracted mesh (default to ``True``) - containers_name (list of str) : List of the names of the fields containers to transfer - on the output extracted tree. - **options: Options related to the extraction. - Returns: - CGNSTree: Extracted submesh (partitioned) - - Extraction can be controled by the optional kwargs: - - - ``graph_part_tool`` (str) -- Partitioning tool used to balance the extracted zones. - Admissible values are ``hilbert, parmetis, ptscotch``. Note that - vertex-located extractions require hilbert partitioning. Defaults to ``hilbert``. - - Important: - - Input tree must have a U-NGon or Structured connectivity - - Partitions must come from a single initial domain on input tree. - - See also: - :func:`create_extractor_from_zsr` takes the same parameters, excepted ``containers_name``, - and returns an Extractor object which can be used to exchange containers more than once through its - ``Extractor.exchange_fields(container_name)`` method. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #extract_from_zsr@start - :end-before: #extract_from_zsr@end - :dedent: 2 - """ - - start = time.time() - extractor = create_extractor_from_zsr(part_tree, zsr_name, comm, **options) - - l_containers_name = [name for name in containers_name] - if transfer_dataset and zsr_name not in l_containers_name: - l_containers_name += [zsr_name] - if l_containers_name: - extractor.exchange_fields(l_containers_name) - end = time.time() - - extract_tree = extractor.get_extract_part_tree() - - # Print some light stats - elts_kind = ['vtx', 'edges', 'faces', 'cells'][extractor.dim] - if extractor.dim == 0: - n_cell = sum([PT.Zone.n_vtx(zone) for zone in PT.iter_all_Zone_t(extract_tree)]) - else: - n_cell = sum([PT.Zone.n_cell(zone) for zone in PT.iter_all_Zone_t(extract_tree)]) - n_cell_all = comm.allreduce(n_cell, MPI.SUM) - mlog.info(f"Extraction from ZoneSubRegion \"{zsr_name}\" completed ({end-start:.2f} s) -- " - f"Extracted tree has locally {mlog.size_to_str(n_cell)} {elts_kind} " - f"(Σ={mlog.size_to_str(n_cell_all)})") - - - return extract_tree - - -def create_extractor_from_zsr(part_tree, zsr_path, comm, **options): - """Same as extract_part_from_zsr, but return the extractor object.""" - # Get zones by domains - - graph_part_tool = options.get("graph_part_tool", "hilbert") - - part_tree_per_dom = dist_from_part.get_parts_per_blocks(part_tree, comm) - - # Get patch for each partitioned zone and group it by domain - patch = list() - location = '' - for domain, part_zones in part_tree_per_dom.items(): - patch_domain = list() - for part_zone in part_zones: - zsr_node = PT.get_node_from_path(part_zone, zsr_path) - if zsr_node is not None: - #Follow BC or GC link - related_node = PT.Subset.ZSRExtent(zsr_node, part_zone) - zsr_node = PT.get_node_from_path(part_zone, related_node) - patch_domain.append(PT.Subset.getPatch(zsr_node)[1]) - location = PT.Subset.GridLocation(zsr_node) - else: # ZSR does not exists on this partition - patch_domain.append(np.empty((1,0), np.int32)) - patch.append(patch_domain) - - # Get location if proc has no zsr - location = comm.allreduce(location, op=MPI.MAX) - - return Extractor(part_tree, patch, location, comm, - graph_part_tool=graph_part_tool) - - - -def extract_part_from_bc_name(part_tree, bc_name, comm, - transfer_dataset=True, - containers_name=[], - **options): - """Extract the submesh defined by the provided BC name from the input volumic - partitioned tree. - - Behaviour and arguments of this function are similar to those of :func:`extract_part_from_zsr`: - ``zsr_name`` becomes ``bc_name`` and optional ``transfer_dataset`` argument allows to - transfer BCDataSet from BC to the extracted mesh (default to ``True``). - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #extract_from_bc_name@start - :end-before: #extract_from_bc_name@end - :dedent: 2 - """ - - # Local copy of the part_tree to add ZSR - l_containers_name = [name for name in containers_name] - local_part_tree = PT.shallow_copy(part_tree) - part_tree_per_dom = dist_from_part.get_parts_per_blocks(local_part_tree, comm) - - # Adding ZSR to tree - there_is_bcdataset = False - for domain, part_zones in part_tree_per_dom.items(): - for part_zone in part_zones: - bc_n = PT.get_node_from_name_and_label(part_zone, bc_name, 'BC_t') - if bc_n is not None: - zsr_bc_n = PT.new_ZoneSubRegion(name=bc_name, bc_name=bc_name, parent=part_zone) - if transfer_dataset: - there_is_bcdataset = set_transfer_dataset(bc_n, zsr_bc_n, PT.Zone.Type(part_zone)) - - if transfer_dataset and comm.allreduce(there_is_bcdataset, MPI.LOR): - l_containers_name.append(bc_name) # not to change the initial containers_name list - - - return extract_part_from_zsr(local_part_tree, bc_name, comm, - transfer_dataset=False, - containers_name=l_containers_name, - **options) - - - -def extract_part_from_family(part_tree, family_name, comm, - transfer_dataset=True, - containers_name=[], - **options): - """Extract the submesh defined by the provided family name from the input volumic - partitioned tree. - - Family related nodes can be labelled either as BC_t or ZoneSubRegion_t, but their - GridLocation must have the same value. They generate a merged output on the resulting extracted tree. - - Behaviour and arguments of this function are similar to those of :func:`extract_part_from_zsr`. - - Warning: - Only U-NGon meshes are managed in this function. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #extract_from_family@start - :end-before: #extract_from_family@end - :dedent: 2 - """ - - if PT.get_value(PT.get_node_from_name(part_tree, 'ZoneType'))=='Structured': - raise RuntimeError(f'extract_part_from_family function is not implemented for Structured meshes.') - - # Local copy of the part_tree to add ZSR - l_containers_name = [name for name in containers_name] - local_part_tree = PT.shallow_copy(part_tree) - part_tree_per_dom = dist_from_part.get_parts_per_blocks(local_part_tree, comm) - - # > Discover family related nodes - in_fam = lambda n : PT.predicate.belongs_to_family(n, family_name, True) - is_regionname = lambda n: PT.get_name(n) in ['BCRegionName', 'GridConnectivityRegionName'] - bc_gc_in_fam = lambda n: PT.get_name(n) in region_node_names - zsr_has_regionname = lambda n: PT.get_label(n)=="ZoneSubRegion_t" and \ - (PT.get_child_from_name(n, 'BCRegionName') is not None or \ - PT.get_child_from_name(n, 'GridConnectivityRegionName') is not None) - fam_to_node_paths = lambda zone, family_name: PT.predicates_to_paths(zone, [lambda n: PT.get_label(n)=='ZoneSubRegion_t' and in_fam]) + \ - PT.predicates_to_paths(zone, ['ZoneBC_t', in_fam]) - - - fam_node_paths = list() - for domain, part_zones in part_tree_per_dom.items(): - dist_zone = PT.new_Zone('Zone') - dist_from_part.discover_nodes_from_matching(dist_zone, part_zones, ['ZoneSubRegion_t' and in_fam], comm, get_value='leaf', child_list=['FamilyName_t', 'GridLocation_t', 'Descriptor_t']) - region_node_names = list() - for zsr_with_regionname_n in PT.get_children_from_predicate(dist_zone, zsr_has_regionname): - region_node = PT.get_child_from_predicate(zsr_with_regionname_n, is_regionname) - region_node_names.append(PT.get_value(region_node)) - child_list = ['AdditionalFamilyName_t', 'FamilyName_t', 'GridLocation_t'] - dist_from_part.discover_nodes_from_matching(dist_zone, part_zones, ['ZoneBC_t', lambda n: in_fam(n) or bc_gc_in_fam(n)], comm, get_value='leaf', child_list=child_list) - dist_from_part.discover_nodes_from_matching(dist_zone, part_zones, ['ZoneGridConnectivity_t', bc_gc_in_fam], comm, get_value='leaf', child_list=child_list) - - fam_node_paths.extend(fam_to_node_paths(dist_zone, family_name)) - - gl_nodes = PT.get_nodes_from_label(dist_zone, 'GridLocation_t') - location = [PT.get_value(n) for n in gl_nodes] - if len(set(location)) > 1: - # Not checking subregion extents, possible ? - raise ValueError(f"Specified family refers to nodes with different GridLocation value : {set(location)}.") - - # Adding ZSR to tree - there_is_bcdataset = dict((path, False) for path in fam_node_paths) - for domain, part_zones in part_tree_per_dom.items(): - for part_zone in part_zones: - - fam_pl = list() - for path in fam_node_paths: - fam_node = PT.get_node_from_path(part_zone, path) - if fam_node is not None: - - if PT.get_label(fam_node)=='BC_t': - bc_name = PT.get_name(fam_node) - if transfer_dataset: - zsr_bc_n = PT.new_ZoneSubRegion(name=bc_name, bc_name=bc_name) - there_is_bcdataset[path] = set_transfer_dataset(fam_node, zsr_bc_n, PT.Zone.Type(part_zone)) - if PT.get_child_from_label(zsr_bc_n, 'DataArray_t') is not None: - PT.add_child(part_zone, zsr_bc_n) - - if PT.get_label(fam_node)=="ZoneSubRegion_t": - if transfer_dataset: - if PT.get_child_from_label(fam_node, 'DataArray_t') is not None: - there_is_bcdataset[path] = True - related_path = PT.Subset.ZSRExtent(fam_node, part_zone) - fam_node = PT.get_node_from_path(part_zone, related_path) - pl_n = PT.get_child_from_name(fam_node, 'PointList') - fam_pl.append(PT.get_value(pl_n)) - - fam_pl = np_utils.concatenate_np_arrays(fam_pl)[1] if len(fam_pl)!=0 else np.zeros(0, dtype=np.int32).reshape((1,-1), order='F') - if fam_pl.size!=0: - fam_pl = np.unique(fam_pl, axis=1) # If pl.size == 0, this line fails with numpy 1.17 - PT.new_ZoneSubRegion(name=family_name, point_list=fam_pl, loc=location[0], parent=part_zone) - - # Synchronize container names - for node_path, there_is in there_is_bcdataset.items(): - if transfer_dataset and comm.allreduce(there_is, MPI.LOR): - node_name = node_path.split('/')[-1] - if node_name not in l_containers_name: - l_containers_name.append(node_name) # not to change the initial containers_name list - - return extract_part_from_zsr(local_part_tree, family_name, comm, - transfer_dataset=False, - containers_name=l_containers_name, - **options) diff --git a/maia/algo/part/extract_part_s.py b/maia/algo/part/extract_part_s.py deleted file mode 100644 index 3273396a..00000000 --- a/maia/algo/part/extract_part_s.py +++ /dev/null @@ -1,255 +0,0 @@ -import maia -import maia.pytree as PT -from maia.factory import dist_from_part -from maia.factory.partitioning.split_S.part_zone import compute_face_gnum -from maia.utils import s_numbering -from .extraction_utils import LOC_TO_DIM, DIMM_TO_DIMF, build_intersection_numbering, discover_containers -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype - -import numpy as np - - -parent_lnum_path = {'Vertex' :'parent_lnum_vtx', - 'IFaceCenter':'parent_lnum_cell', - 'JFaceCenter':'parent_lnum_cell', - 'KFaceCenter':'parent_lnum_cell', - 'CellCenter' :'parent_lnum_cell'} - -def exchange_field_one_domain(part_tree, extract_zones, mesh_dim, etb, container_name, comm) : - - part_tree_per_dom = dist_from_part.get_parts_per_blocks(part_tree, comm).values() - assert len(part_tree_per_dom) == 1 - part_zones=list(part_tree_per_dom)[0] - - # > Retrieve fields name + GridLocation + PointRange if container - # is not know by every partition - mask_container, grid_location, partial_field = discover_containers(part_zones, container_name, 'PointRange', 'IndexRange_t', comm) - if mask_container is None: - return - assert grid_location in ['Vertex', 'IFaceCenter', 'JFaceCenter', 'KFaceCenter', 'CellCenter'] - - if partial_field: - part1_pr, part1_gnum1, part1_in_part2 = build_intersection_numbering(part_tree, extract_zones, mesh_dim, container_name, grid_location, etb, comm) - - for i_zone, extract_zone in enumerate(extract_zones): - - zone_name = PT.get_name(extract_zone) - part_zone = PT.get_node_from_name_and_label(part_tree, zone_name, 'Zone_t') - - if partial_field and part1_gnum1[i_zone].size==0: - continue # Pass if no recovering - - if PT.get_label(mask_container) == 'FlowSolution_t': - FS_ep = PT.new_FlowSolution(container_name, loc=DIMM_TO_DIMF[mesh_dim][grid_location], parent=extract_zone) - elif PT.get_label(mask_container) == 'ZoneSubRegion_t': - FS_ep = PT.new_ZoneSubRegion(container_name, loc=DIMM_TO_DIMF[mesh_dim][grid_location], parent=extract_zone) - else: - raise TypeError - - # Add partial numbering to node - if partial_field: - pr_n = PT.new_IndexRange(value=part1_pr[i_zone], parent=FS_ep) - gn_n = PT.maia.newGlobalNumbering({'Index' : part1_gnum1[i_zone]}, parent=FS_ep) - - for fld_node in PT.get_children_from_label(mask_container, 'DataArray_t'): - fld_name = PT.get_name(fld_node) - fld_path = f"{container_name}/{fld_name}" - - fld_data = PT.get_value(PT.get_node_from_path(part_zone,fld_path)) - - if partial_field: - extract_fld_data = fld_data.flatten(order='F')[part1_in_part2[i_zone]] - if PT.get_label(FS_ep)=='FlowSolution_t': - extract_fld_data = extract_fld_data.reshape(np.diff(part1_pr[i_zone])[:,0]+1, order='F') - else: - parent_part1_pl = etb[zone_name][parent_lnum_path[grid_location]] - extract_fld_data = fld_data.flatten(order='F')[parent_part1_pl-1] - if PT.get_label(FS_ep)=='FlowSolution_t': - zone_elt_dim = PT.Zone.VertexSize(extract_zone) if grid_location=='Vertex' else PT.Zone.CellSize(extract_zone) - extract_fld_data = extract_fld_data.reshape(zone_elt_dim, order='F') - - PT.new_DataArray(fld_name, extract_fld_data, parent=FS_ep) - - -def exchange_field_s(part_tree, extract_tree, mesh_dim, etb, container_names, comm) : - # Get zones by domains (only one domain for now) - extract_part_tree_per_dom = dist_from_part.get_parts_per_blocks(extract_tree, comm) - for container_name in container_names: - for i_domain, dom_ep_part_zones in enumerate(extract_part_tree_per_dom.items()): - dom_path = dom_ep_part_zones[0] - extracted_zones = dom_ep_part_zones[1] - exchange_field_one_domain(part_tree, extracted_zones, mesh_dim, etb[dom_path], container_name, comm) - - -def extract_part_one_domain_s(part_zones, point_range, location, comm): - extract_zones = list() - lvtx_gn = list() - lcell_gn = list() - extract_pr_min_per_pzone_l = dict() - - for i_part, part_zone in enumerate(part_zones): - pr = point_range[i_part] - if pr.size!=0: - extract_pr_min_per_pzone_l[PT.get_name(part_zone)] = np.array([min(pr[0,0],pr[0,1]) - 1, - min(pr[1,0],pr[1,1]) - 1, - min(pr[2,0],pr[2,1]) - 1]) - extract_pr_min_per_pzone_l = comm.allgather(extract_pr_min_per_pzone_l) - extract_pr_min_per_pzone_all = {k: v for d in extract_pr_min_per_pzone_l for k, v in d.items()} - - etb = dict() - - dim = LOC_TO_DIM[location] - - for i_part, part_zone in enumerate(part_zones): - zone_name = PT.get_name(part_zone) - zone_dim = PT.get_value(part_zone) - pr = np.copy(point_range[i_part]) - - extract_zone = PT.new_Zone(zone_name, type='Structured', size=np.zeros((3,3), dtype=np.int32)) - - if pr.size==0: - lvtx_gn.append(np.empty(0, dtype=pdm_gnum_dtype)) - lcell_gn.append(np.empty(0, dtype=pdm_gnum_dtype)) - extract_zones.append(extract_zone) - continue - - size_per_dim = np.diff(pr)[:,0] - mask = np.ones(3, dtype=bool) - if location=='Vertex': - idx = np.where(size_per_dim==0)[0] - mask[idx] = False - n_dim_pop = idx.size - if n_dim_pop in [2,3]: - raise NotImplementedError(f'Asked extraction is 0D or 1D (n_dim_pop={n_dim_pop})') - extract_dir = idx[0] - elif 'FaceCenter' in location: - extract_dir = PT.Subset.normal_axis(PT.new_BC(point_range=pr, loc=location)) - mask[extract_dir] = False - n_dim_pop = 1 - else: - n_dim_pop = 0 - if location!='Vertex': - pr[mask,1]+=1 - size_per_dim+=1 - - # n_dim_pop = 0 - extract_zone_dim = np.zeros((3-n_dim_pop,3), dtype=np.int32) - extract_zone_dim[:,0] = size_per_dim[mask]+1 # size_per_dim[mask]+1 - extract_zone_dim[:,1] = size_per_dim[mask] # size_per_dim[mask] - PT.set_value(extract_zone, extract_zone_dim) - - # > Get coordinates - cx, cy, cz = PT.Zone.coordinates(part_zone) - extract_cx = cx[pr[0,0]-1:pr[0,1], pr[1,0]-1:pr[1,1], pr[2,0]-1:pr[2,1]] - extract_cy = cy[pr[0,0]-1:pr[0,1], pr[1,0]-1:pr[1,1], pr[2,0]-1:pr[2,1]] - extract_cz = cz[pr[0,0]-1:pr[0,1], pr[1,0]-1:pr[1,1], pr[2,0]-1:pr[2,1]] - extract_cx = np.reshape(extract_cx, size_per_dim[mask]+1) - extract_cy = np.reshape(extract_cy, size_per_dim[mask]+1) - extract_cz = np.reshape(extract_cz, size_per_dim[mask]+1) - PT.new_GridCoordinates(fields={'CoordinateX':extract_cx, - 'CoordinateY':extract_cy, - 'CoordinateZ':extract_cz}, - parent=extract_zone) - - # > Set GlobalNumbering - vtx_per_dir = zone_dim[:,0] - cell_per_dir = zone_dim[:,1] - - gn = PT.get_child_from_name(part_zone, ':CGNS#GlobalNumbering') - gn_vtx = PT.get_value(PT.get_node_from_name(gn, 'Vertex')) - gn_face = PT.get_value(PT.get_node_from_name(gn, 'Face')) - gn_cell = PT.get_value(PT.get_node_from_name(gn, 'Cell')) - - i_ar_cell = np.arange(min(pr[0]), max(pr[0])) - j_ar_cell = np.arange(min(pr[1]), max(pr[1])).reshape(-1,1) - k_ar_cell = np.arange(min(pr[2]), max(pr[2])).reshape(-1,1,1) - - if n_dim_pop==1: - ijk_to_faceIndex = [s_numbering.ijk_to_faceiIndex, s_numbering.ijk_to_facejIndex, s_numbering.ijk_to_facekIndex] - - locnum_cell = ijk_to_faceIndex[extract_dir](i_ar_cell, j_ar_cell, k_ar_cell, \ - cell_per_dir, vtx_per_dir).flatten() - lcell_gn.append(gn_face[locnum_cell-1]) - else: - locnum_cell = s_numbering.ijk_to_index(i_ar_cell, j_ar_cell, k_ar_cell, cell_per_dir).flatten() - lcell_gn.append(gn_cell[locnum_cell-1]) - - i_ar_vtx = np.arange(min(pr[0]), max(pr[0])+1) - j_ar_vtx = np.arange(min(pr[1]), max(pr[1])+1).reshape(-1,1) - k_ar_vtx = np.arange(min(pr[2]), max(pr[2])+1).reshape(-1,1,1) - locnum_vtx = s_numbering.ijk_to_index(i_ar_vtx, j_ar_vtx, k_ar_vtx, vtx_per_dir).flatten() - lvtx_gn.append(gn_vtx[locnum_vtx - 1]) - - etb[zone_name] = {'parent_lnum_vtx' :locnum_vtx, - 'parent_lnum_cell':locnum_cell} - - # > Get joins without post-treating PRs - for zgc_n in PT.get_children_from_label(part_zone, 'ZoneGridConnectivity_t'): - extract_zgc = PT.new_ZoneGridConnectivity(PT.get_name(zgc_n), parent=extract_zone) - for gc_n in PT.get_children_from_predicate(zgc_n, lambda n: PT.maia.conv.is_intra_gc(PT.get_name(n))): - gc_pr = PT.get_value(PT.get_child_from_name(gc_n,"PointRange")) - intersection = maia.factory.partitioning.split_S.part_zone.intersect_pr(gc_pr, pr) - if intersection is not None: - - transform = PT.GridConnectivity.Transform(gc_n) - new_gc_prd = PT.utils.gc_transform_window(gc_n, intersection) - - # > Update joins PRs - min_cur = extract_pr_min_per_pzone_all[zone_name] - try: - min_opp = extract_pr_min_per_pzone_all[PT.get_value(gc_n)] - except KeyError: - min_opp = None - - new_gc_pr = np.copy(intersection) - new_gc_pr[0,:] -= min_cur[0] - new_gc_pr[1,:] -= min_cur[1] - new_gc_pr[2,:] -= min_cur[2] - - if min_opp is not None: - new_gc_prd[0,:] -= min_opp[0] - new_gc_prd[1,:] -= min_opp[1] - new_gc_prd[2,:] -= min_opp[2] - if dim<3: - new_gc_pr = np.delete(new_gc_pr , extract_dir, 0) - new_gc_prd = np.delete(new_gc_prd, transform[extract_dir]-1, 0) - - gc_name = PT.get_name(gc_n) - gc_donorname = PT.get_value(gc_n) - extract_gc_n = PT.new_GridConnectivity1to1(gc_name, donor_name=gc_donorname, point_range=new_gc_pr, point_range_donor=new_gc_prd, parent=extract_zgc) - - extract_zones.append(extract_zone) - - # > Create GlobalNumbering - partial_gnum_vtx = maia.algo.part.point_cloud_utils.create_sub_numbering(lvtx_gn, comm) - partial_gnum_cell = maia.algo.part.point_cloud_utils.create_sub_numbering(lcell_gn, comm) - if dim==3: - cell_size = dist_from_part._recover_dist_block_size(extract_zones, comm) - - if len(partial_gnum_vtx)!=0: - for i_part, extract_zone in enumerate(extract_zones): - PT.maia.newGlobalNumbering({'Vertex' : partial_gnum_vtx [i_part], - 'Cell' : partial_gnum_cell[i_part]}, - parent=extract_zone) - - # > Retrive missing gnum if 3d - if dim==3 and PT.Zone.n_cell(extract_zone)!=0: - cell_ijk = s_numbering.index_to_ijk(partial_gnum_cell[i_part], cell_size[:,1]) - cell_range = np.array([[min(cell_ijk[0]),max(cell_ijk[0])], - [min(cell_ijk[1]),max(cell_ijk[1])], - [min(cell_ijk[2]),max(cell_ijk[2])]]) - cell_window = cell_range - cell_window[:,1] +=1 - - dist_cell_per_dir = cell_size[:,1] - face_lntogn = compute_face_gnum(dist_cell_per_dir, cell_window) - - gn_node = PT.maia.getGlobalNumbering(extract_zone) - PT.new_node("CellRange", "IndexRange_t", cell_range, parent=gn_node) - PT.new_DataArray("CellSize", cell_size[:,1], parent=gn_node) - PT.new_DataArray("Face", face_lntogn, parent=gn_node) - else: - assert len(partial_gnum_cell)==0 - - return extract_zones,etb - diff --git a/maia/algo/part/extract_part_u.py b/maia/algo/part/extract_part_u.py deleted file mode 100644 index 5a2cba50..00000000 --- a/maia/algo/part/extract_part_u.py +++ /dev/null @@ -1,301 +0,0 @@ -import time -import mpi4py.MPI as MPI - -import maia -import maia.pytree as PT -import maia.pytree.maia as MT -from maia.factory import dist_from_part -from maia.transfer import utils as TEU -from maia.utils import np_utils, layouts -from .extraction_utils import local_pl_offset, LOC_TO_DIM, DIMM_TO_DIMF,\ - get_partial_container_stride_and_order, discover_containers -from .point_cloud_utils import create_sub_numbering -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype - -import numpy as np - -import Pypdm.Pypdm as PDM - - -def exchange_field_one_domain(part_zones, extract_zone, mesh_dim, exch_tool_box, container_name, comm) : - - # > Retrieve fields name + GridLocation + PointList if container - # is not know by every partition - mask_container, grid_location, partial_field = discover_containers(part_zones, container_name, 'PointList', 'IndexArray_t', comm) - if mask_container is None: - return - assert grid_location in ['Vertex', 'FaceCenter', 'CellCenter'] - - - # > FlowSolution node def by zone - if extract_zone is not None : - if PT.get_label(mask_container) == 'FlowSolution_t': - FS_ep = PT.new_FlowSolution(container_name, loc=DIMM_TO_DIMF[mesh_dim][grid_location], parent=extract_zone) - elif PT.get_label(mask_container) == 'ZoneSubRegion_t': - FS_ep = PT.new_ZoneSubRegion(container_name, loc=DIMM_TO_DIMF[mesh_dim][grid_location], parent=extract_zone) - else: - raise TypeError - - - # > Get PTP and parentElement for the good location - ptp = exch_tool_box['part_to_part'][grid_location] - - # LN_TO_GN - _grid_location = {"Vertex" : "Vertex", "FaceCenter" : "Element", "CellCenter" : "Cell"} - - if extract_zone is not None: - elt_n = extract_zone if grid_location!='FaceCenter' else PT.Zone.NGonNode(extract_zone) - if elt_n is None :return - part1_elt_gnum_n = PT.maia.getGlobalNumbering(elt_n, _grid_location[grid_location]) - part1_ln_to_gn = [PT.get_value(part1_elt_gnum_n)] - - # Get reordering informations if point_list - # https://stackoverflow.com/questions/8251541/numpy-for-every-element-in-one-array-find-the-index-in-another-array - if partial_field: - pl_gnum1, stride = get_partial_container_stride_and_order(part_zones, container_name, grid_location, ptp, comm) - - # > Field exchange - for fld_node in PT.get_children_from_label(mask_container, 'DataArray_t'): - fld_name = PT.get_name(fld_node) - fld_path = f"{container_name}/{fld_name}" - - if partial_field: - # Get field and organize it according to the gnum1_come_from arrays order - fld_data = list() - for i_part, part_zone in enumerate(part_zones) : - fld_n = PT.get_node_from_path(part_zone,fld_path) - fld_data_tmp = PT.get_value(fld_n) if fld_n is not None else np.empty(0, dtype=np.float64) - fld_data.append(fld_data_tmp[pl_gnum1[i_part]]) - p2p_type = PDM._PDM_PART_TO_PART_DATA_DEF_ORDER_GNUM1_COME_FROM - - else : - fld_data = [PT.get_node_from_path(part_zone,fld_path)[1] for part_zone in part_zones] - stride = 1 - p2p_type = PDM._PDM_PART_TO_PART_DATA_DEF_ORDER_PART2 - - # Reverse iexch - req_id = ptp.reverse_iexch(PDM._PDM_MPI_COMM_KIND_P2P, - p2p_type, - fld_data, - part2_stride=stride) - part1_stride, part1_data = ptp.reverse_wait(req_id) - - # Interpolation and placement - if extract_zone is not None: - i_part = 0 - if part1_data[i_part].size!=0: - PT.new_DataArray(fld_name, part1_data[i_part], parent=FS_ep) - - # Build PL with the last exchange stride - if partial_field: - if len(part1_data)!=0 and part1_data[0].size!=0: - new_point_list = np.where(part1_stride[0]==1)[0] if part1_data[0].size!=0 else np.empty(0, dtype=np.int32) - point_list = new_point_list + local_pl_offset(extract_zone, LOC_TO_DIM[grid_location])+1 - PT.new_IndexArray(name='PointList', value=point_list.reshape((1,-1), order='F'), parent=FS_ep) - partial_part1_lngn = [part1_ln_to_gn[0][new_point_list]] - else: - partial_part1_lngn = [] - - # Update global numbering in FS - partial_gnum = create_sub_numbering(partial_part1_lngn, comm) - if extract_zone is not None and len(partial_gnum)!=0: - PT.maia.newGlobalNumbering({'Index' : partial_gnum[0]}, parent=FS_ep) - - if part1_data[0].size==0: - PT.rm_child(extract_zone, FS_ep) - - -def exchange_field_u(part_tree, extract_part_tree, mesh_dim, exch_tool_box, container_names, comm) : - # Get zones by domains (only one domain for now) - part_tree_per_dom = dist_from_part.get_parts_per_blocks(part_tree, comm) - - # Get zone from extractpart - extract_zones = PT.get_all_Zone_t(extract_part_tree) - assert len(extract_zones) <= 1 - extract_zone = extract_zones[0] if len(extract_zones)!=0 else None - - for container_name in container_names: - for i_domain, dom_part_zones in enumerate(part_tree_per_dom.items()): - dom_path = dom_part_zones[0] - part_zones = dom_part_zones[1] - exchange_field_one_domain(part_zones, extract_zone, mesh_dim, exch_tool_box[dom_path], \ - container_name, comm) - - -def extract_part_one_domain_u(part_zones, point_list, location, comm, - # equilibrate=True, - graph_part_tool="hilbert"): - """ - Prepare PDM extract_part object and perform the extraction of one domain. - - TODO : AJOUTER LE CHOIX PARTIONNEMENT - """ - equilibrate=True - - dim = LOC_TO_DIM[location] - - n_part_in = len(part_zones) - n_part_out = 1 if equilibrate else n_part_in - - pdm_ep = PDM.ExtractPart(dim, # face/cells - n_part_in, - n_part_out, - equilibrate, - eval(f"PDM._PDM_SPLIT_DUAL_WITH_{graph_part_tool.upper()}"), - True, - comm) - - # > Discover BCs - dist_zone = PT.new_Zone('Zone') - gdom_bcs_path_per_dim = {"CellCenter":None, "FaceCenter":None, "EdgeCenter":None, "Vertex":None} - for bc_type, dim_name in enumerate(gdom_bcs_path_per_dim): - if LOC_TO_DIM[dim_name]<=dim: - is_dim_bc = lambda n: PT.get_label(n)=="BC_t" and\ - PT.Subset.GridLocation(n)==dim_name - dist_from_part.discover_nodes_from_matching(dist_zone, part_zones, ["ZoneBC_t", is_dim_bc], comm, child_list=['GridLocation']) - gdom_bcs_path_per_dim[dim_name] = PT.predicates_to_paths(dist_zone, ['ZoneBC_t',is_dim_bc]) - n_gdom_bcs = len(gdom_bcs_path_per_dim[dim_name]) - pdm_ep.part_n_group_set(bc_type+1, n_gdom_bcs) - - # Loop over domain zone : preparing extract part - for i_part, part_zone in enumerate(part_zones): - # Get NGon + NFac - cx, cy, cz = PT.Zone.coordinates(part_zone) - vtx_coords = np_utils.interweave_arrays([cx,cy,cz]) - - ngon = PT.Zone.NGonNode(part_zone) - nface = PT.Zone.NFaceNode(part_zone) - - cell_face_idx = PT.get_child_from_name(nface, "ElementStartOffset" )[1] - cell_face = PT.get_child_from_name(nface, "ElementConnectivity")[1] - face_vtx_idx = PT.get_child_from_name(ngon, "ElementStartOffset" )[1] - face_vtx = PT.get_child_from_name(ngon, "ElementConnectivity")[1] - - vtx_ln_to_gn, _, face_ln_to_gn, cell_ln_to_gn = TEU.get_entities_numbering(part_zone) - - n_cell = cell_ln_to_gn.shape[0] - n_face = face_ln_to_gn.shape[0] - n_edge = 0 - n_vtx = vtx_ln_to_gn .shape[0] - - pdm_ep.part_set(i_part, - n_cell, n_face, n_edge, n_vtx, - cell_face_idx, cell_face , - None, None, None, - face_vtx_idx , face_vtx , - cell_ln_to_gn, face_ln_to_gn, - None, - vtx_ln_to_gn , vtx_coords) - - pdm_ep.selected_lnum_set(i_part, point_list[i_part][0] - local_pl_offset(part_zone, dim) - 1) - - - # Add BCs info - bc_type = 1 - for dim_name, gdom_bcs_path in gdom_bcs_path_per_dim.items(): - if LOC_TO_DIM[dim_name]<=dim: - for i_bc, bc_path in enumerate(gdom_bcs_path): - bc_n = PT.get_node_from_path(part_zone, bc_path) - bc_pl = PT.get_value(PT.get_child_from_name(bc_n, 'PointList'))[0] \ - if bc_n is not None else np.empty(0, np.int32) - bc_gn = PT.get_value(MT.getGlobalNumbering(bc_n, 'Index')) if bc_n is not None else np.empty(0, pdm_gnum_dtype) - pdm_ep.part_group_set(i_part, i_bc, bc_type, bc_pl-local_pl_offset(part_zone, LOC_TO_DIM[dim_name]) -1 , bc_gn) - bc_type +=1 - - pdm_ep.compute() - - # > Reconstruction du maillage de l'extract part - n_extract_cell = pdm_ep.n_entity_get(0, PDM._PDM_MESH_ENTITY_CELL) - n_extract_face = pdm_ep.n_entity_get(0, PDM._PDM_MESH_ENTITY_FACE) - n_extract_edge = pdm_ep.n_entity_get(0, PDM._PDM_MESH_ENTITY_EDGE) - n_extract_vtx = pdm_ep.n_entity_get(0, PDM._PDM_MESH_ENTITY_VTX ) - - size_by_dim = {0: [[n_extract_vtx, 0 , 0]], # not yet implemented - 1: None , # not yet implemented - 2: [[n_extract_vtx, n_extract_face, 0]], - 3: [[n_extract_vtx, n_extract_cell, 0]] } - - # > ExtractPart zone construction - extract_zone = PT.new_Zone(PT.maia.conv.add_part_suffix('Zone', comm.Get_rank(), 0), - size=size_by_dim[dim], - type='Unstructured') - - ep_vtx_ln_to_gn = pdm_ep.ln_to_gn_get(0,PDM._PDM_MESH_ENTITY_VTX) - PT.maia.newGlobalNumbering({"Vertex" : ep_vtx_ln_to_gn}, parent=extract_zone) - - # > Grid coordinates - cx, cy, cz = layouts.interlaced_to_tuple_coords(pdm_ep.vtx_coord_get(0)) - extract_grid_coord = PT.new_GridCoordinates(parent=extract_zone) - PT.new_DataArray('CoordinateX', cx, parent=extract_grid_coord) - PT.new_DataArray('CoordinateY', cy, parent=extract_grid_coord) - PT.new_DataArray('CoordinateZ', cz, parent=extract_grid_coord) - - if dim == 0: - PT.maia.newGlobalNumbering({'Cell' : np.empty(0, dtype=ep_vtx_ln_to_gn.dtype)}, parent=extract_zone) - - # > NGON - if dim >= 2: - ep_face_vtx_idx, ep_face_vtx = pdm_ep.connectivity_get(0, PDM._PDM_CONNECTIVITY_TYPE_FACE_VTX) - ngon_n = PT.new_NGonElements( 'NGonElements', - erange = [1, n_extract_face], - ec = ep_face_vtx, - eso = ep_face_vtx_idx, - parent = extract_zone) - - ep_face_ln_to_gn = pdm_ep.ln_to_gn_get(0, PDM._PDM_MESH_ENTITY_FACE) - PT.maia.newGlobalNumbering({'Element' : ep_face_ln_to_gn}, parent=ngon_n) - if dim == 2: - PT.maia.newGlobalNumbering({'Cell' : ep_face_ln_to_gn}, parent=extract_zone) - - # > NFACES - if dim == 3: - ep_cell_face_idx, ep_cell_face = pdm_ep.connectivity_get(0, PDM._PDM_CONNECTIVITY_TYPE_CELL_FACE) - nface_n = PT.new_NFaceElements('NFaceElements', - erange = [n_extract_face+1, n_extract_face+n_extract_cell], - ec = ep_cell_face, - eso = ep_cell_face_idx, - parent = extract_zone) - - ep_cell_ln_to_gn = pdm_ep.ln_to_gn_get(0, PDM._PDM_MESH_ENTITY_CELL) - PT.maia.newGlobalNumbering({'Element' : ep_cell_ln_to_gn}, parent=nface_n) - PT.maia.newGlobalNumbering({'Cell' : ep_cell_ln_to_gn}, parent=extract_zone) - - maia.algo.nface_to_pe(extract_zone, comm) - - # - Get BCs - zonebc_n = PT.new_ZoneBC(parent=extract_zone) - bc_type = 1 - for dim_name, gdom_bcs_path in gdom_bcs_path_per_dim.items(): - if LOC_TO_DIM[dim_name]<=dim: - for i_bc, bc_path in enumerate(gdom_bcs_path): - bc_info = pdm_ep.extract_part_group_get(0, i_bc, bc_type) - bc_pl = bc_info['group_entity'] +1 - bc_gn = bc_info['group_entity_ln_to_gn'] - if bc_pl.size != 0: - bc_name = bc_path.split('/')[-1] - bc_n = PT.new_BC(bc_name, point_list=bc_pl.reshape((1,-1), order='F'), loc=dim_name, parent=zonebc_n) - PT.maia.newGlobalNumbering({'Index':bc_gn}, parent=bc_n) - bc_type +=1 - - # - Get PTP by vertex and cell - ptp = dict() - if equilibrate: - ptp['Vertex'] = pdm_ep.part_to_part_get(PDM._PDM_MESH_ENTITY_VTX) - if dim >= 2: # NGON - ptp['FaceCenter'] = pdm_ep.part_to_part_get(PDM._PDM_MESH_ENTITY_FACE) - if dim == 3: # NFACE - ptp['CellCenter'] = pdm_ep.part_to_part_get(PDM._PDM_MESH_ENTITY_CELL) - - # - Get parent elt - parent_elt = dict() - parent_elt['Vertex'] = pdm_ep.parent_ln_to_gn_get(0,PDM._PDM_MESH_ENTITY_VTX) - if dim >= 2: # NGON - parent_elt['FaceCenter'] = pdm_ep.parent_ln_to_gn_get(0,PDM._PDM_MESH_ENTITY_FACE) - if dim == 3: # NFACE - parent_elt['CellCenter'] = pdm_ep.parent_ln_to_gn_get(0,PDM._PDM_MESH_ENTITY_CELL) - - exch_tool_box = {'part_to_part' : ptp, 'parent_elt' : parent_elt} - - return [extract_zone], exch_tool_box - - diff --git a/maia/algo/part/extraction_utils.py b/maia/algo/part/extraction_utils.py deleted file mode 100644 index a1812e83..00000000 --- a/maia/algo/part/extraction_utils.py +++ /dev/null @@ -1,215 +0,0 @@ -import maia.pytree as PT -from maia.utils import np_utils, s_numbering, as_pdm_gnum -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.factory import dist_from_part -from .point_cloud_utils import create_sub_numbering - -import numpy as np - -LOC_TO_DIM = {'Vertex':0, - 'EdgeCenter':1, - 'FaceCenter':2, 'IFaceCenter':2, 'JFaceCenter':2, 'KFaceCenter':2, - 'CellCenter':3} - -DIMM_TO_DIMF = { 0: {'Vertex':'Vertex'}, - # 1: {'Vertex': None, 'EdgeCenter':None, 'FaceCenter':None, 'CellCenter':None}, - 2: {'Vertex':'Vertex', 'EdgeCenter':'EdgeCenter', 'FaceCenter':'CellCenter'}, - 3: {'Vertex':'Vertex', 'EdgeCenter':'EdgeCenter', 'FaceCenter':'FaceCenter', 'CellCenter':'CellCenter'}} - -def discover_containers(part_zones, container_name, patch_name, patch_type, comm): - mask_zone = ['MaskedZone', None, [], 'Zone_t'] - dist_from_part.discover_nodes_from_matching(mask_zone, part_zones, container_name, comm, \ - child_list=['GridLocation', 'BCRegionName', 'GridConnectivityRegionName']) - - fields_query = lambda n: PT.get_label(n) in ['DataArray_t', patch_type] - dist_from_part.discover_nodes_from_matching(mask_zone, part_zones, [container_name, fields_query], comm) - mask_container = PT.get_child_from_name(mask_zone, container_name) - if mask_container is None: - raise ValueError(f"[maia-extract_part] asked container \"{container_name}\" for exchange is not in tree") - if PT.get_child_from_label(mask_container, 'DataArray_t') is None: - return None, '', False - patch_node = PT.get_child_from_name(mask_container, patch_name) - - # > Manage BC and GC ZSR - ref_zsr_node = mask_container - bc_descriptor_n = PT.get_child_from_name(mask_container, 'BCRegionName') - gc_descriptor_n = PT.get_child_from_name(mask_container, 'GridConnectivityRegionName') - assert not (bc_descriptor_n and gc_descriptor_n) - if bc_descriptor_n is not None: - bc_name = PT.get_value(bc_descriptor_n) - dist_from_part.discover_nodes_from_matching(mask_zone, part_zones, ['ZoneBC_t', bc_name], comm, child_list=[patch_name, 'GridLocation_t']) - ref_zsr_node = PT.get_child_from_predicates(mask_zone, f'ZoneBC_t/{bc_name}') - patch_node = PT.get_child_from_predicates(ref_zsr_node, f'{patch_name}') - assert patch_node is not None, 'Asked patch unfound for subregion extent.' - elif gc_descriptor_n is not None: - gc_name = PT.get_value(gc_descriptor_n) - dist_from_part.discover_nodes_from_matching(mask_zone, part_zones, ['ZoneGridConnectivity_t', gc_name], comm, child_list=[patch_name, 'GridLocation_t']) - ref_zsr_node = PT.get_child_from_predicates(mask_zone, f'ZoneGridConnectivity_t/{gc_name}') - patch_node = PT.get_child_from_predicates(ref_zsr_node, f'{patch_name}') - assert patch_node is not None, 'Asked patch unfound for subregion extent.' - - if PT.get_label(mask_container)=='ZoneSubRegion_t' and patch_node is None: - raise ValueError('Asked patch unfound for ZSR container extent.') - - grid_location = PT.Subset.GridLocation(ref_zsr_node) - partial_field = PT.get_child_from_name(ref_zsr_node, patch_name) is not None - return mask_container, grid_location, partial_field - -def local_pl_offset(part_zone, dim): - """ - Return the shift related to the element of the dimension to apply to a point_list so it starts to 1. - This function assumes that there will be only one type of element per dimension on zone - (because isosurface and extract_part returns trees with this property). - Works only for ngon/nface 3D meshes and elements meshes. - """ - if dim == 3: - nface = PT.Zone.NFaceNode(part_zone) - if nface is None: - # Extract_part and isosurfaces use trees with NGon and NFace - raise ValueError("NGon trees must have NFace connectivity to extract pl offset") - return PT.Element.Range(nface)[0] - 1 - elif dim == 2: - if PT.Zone.has_ngon_elements(part_zone): - ngon = PT.Zone.NGonNode(part_zone) - return PT.Element.Range(ngon)[0] - 1 - else: - tri_or_quad_elts = lambda n: PT.get_label(n)=='Elements_t' and PT.Element.Dimension(n)==2 - elt_n = PT.get_child_from_predicate(part_zone, tri_or_quad_elts) - return PT.Element.Range(elt_n)[0] - 1 - elif dim == 1: - bar_elts = lambda n: PT.get_label(n)=='Elements_t' and PT.Element.Dimension(n)==1 - elt_n = PT.get_child_from_predicate(part_zone, bar_elts) - return PT.Element.Range(elt_n)[0] - 1 - else: - return 0 - -def get_relative_pl(container, part_zone): - """Return the point_list node related to a container (from BC, GC or itself).""" - if PT.get_label(container)=="FlowSolution_t": - relative_n = container - else: - relative_n = PT.get_node_from_path(part_zone, PT.Subset.ZSRExtent(container, part_zone)) - return PT.get_child_from_name(relative_n, "PointList") - -def get_partial_container_stride_and_order(part_zones, container_name, gridLocation, ptp, comm): - """ - Return two list of arrays : - - pl_gnum1 : the order to apply to container fields defined with a point_list to fit the gnum1_come_from order - - stride : stride of shape gnum1_come_from indexing which elt will get data - """ - pl_gnum1 = list() - stride = list() - - for i_part, part_zone in enumerate(part_zones): - container = PT.get_child_from_name(part_zone, container_name) - if container is not None: - # > Get the right node to get PL (if ZSR linked to BC or GC) - point_list_n = get_relative_pl(container, part_zone) - point_list = PT.get_value(point_list_n)[0] - local_pl_offset(part_zone, LOC_TO_DIM[gridLocation]) # Gnum start at 1 - - # Get p2p gnums - part_gnum1_idx = ptp.get_gnum1_come_from() [i_part]['come_from_idx'] # Get partition order - part_gnum1 = ptp.get_gnum1_come_from() [i_part]['come_from'] # Get partition order - ref_lnum2 = ptp.get_referenced_lnum2()[i_part] # Get partition order - - if container is None or point_list.size==0 or ref_lnum2.size==0: - stride_tmp = np.zeros(part_gnum1_idx[-1],dtype=np.int32) - pl_gnum1_tmp = np.empty(0,dtype=np.int32) - stride .append(stride_tmp) - pl_gnum1.append(pl_gnum1_tmp) - else: - order = np.argsort(ref_lnum2) # Sort order of point_list () - # Next lines create 2 arrays : pl_mask and true_idx - # pl_mask (size=pl.size, bool) tell for each pl element if it appears in ref_lnum2 - # true_idx (size=pl_mask.sum(), int) give for each pl element its position in ref_lnum2 - # (if pl_mask is True) - idx = np.searchsorted(ref_lnum2,point_list,sorter=order) - # Take is equivalent to order[idx], but if idx > order.size, last elt of order is taken - pl_mask = point_list==ref_lnum2[np.take(order, idx, mode='clip')] - true_idx = idx[pl_mask] - - # Number of part1 elements in an element of part2 - n_elt_of1_in2 = np.diff(part_gnum1_idx)[true_idx] - - sort_true_idx = np.argsort(true_idx) - - # PL in part2 order - pl_gnum1_tmp = np.arange(0, point_list.shape[0], dtype=np.int32)[pl_mask][sort_true_idx] - pl_gnum1_tmp = np.repeat(pl_gnum1_tmp, n_elt_of1_in2) - pl_gnum1.append(pl_gnum1_tmp) - - # PL in gnum1 order - pl_to_gnum1_start = part_gnum1_idx[true_idx] - pl_to_gnum1_stop = pl_to_gnum1_start+n_elt_of1_in2 - pl_to_gnum1 = np_utils.multi_arange(pl_to_gnum1_start, pl_to_gnum1_stop) - - # Stride variable - stride_tmp = np.zeros(part_gnum1_idx[-1], dtype=np.int32) - stride_tmp[pl_to_gnum1] = 1 - stride.append(stride_tmp) - - return pl_gnum1, stride - -def build_intersection_numbering(part_tree, extract_zones, mesh_dim, container_name, grid_location, etb, comm): - - parent_lnum_path = {'Vertex' :'parent_lnum_vtx', - 'IFaceCenter':'parent_lnum_cell', - 'JFaceCenter':'parent_lnum_cell', - 'KFaceCenter':'parent_lnum_cell', - 'CellCenter' :'parent_lnum_cell'} - LOC_TO_GNUM = {'Vertex' :'Vertex', - 'IFaceCenter':'Face', - 'JFaceCenter':'Face', - 'KFaceCenter':'Face', - 'CellCenter' :'Cell', - } - - part1_pr = list() - part1_in_part2 = list() - partial_gnum = list() - for extract_zone in extract_zones: - - zone_name = PT.get_name(extract_zone) - part_zone = PT.get_node_from_name_and_label(part_tree, zone_name, 'Zone_t') - - parent_part1_pl = etb[zone_name][parent_lnum_path[grid_location]] - - subset_n = PT.get_child_from_name(part_zone,container_name) - if subset_n is not None: - pr = PT.get_value(PT.Subset.getPatch(subset_n)) - i_ar = np.arange(min(pr[0]), max(pr[0])+1) - j_ar = np.arange(min(pr[1]), max(pr[1])+1).reshape(-1,1) - k_ar = np.arange(min(pr[2]), max(pr[2])+1).reshape(-1,1,1) - part2_pl = s_numbering.ijk_to_index_from_loc(i_ar, j_ar, k_ar, grid_location, PT.Zone.VertexSize(part_zone)).flatten() - - lnum2 = np.searchsorted(part2_pl, parent_part1_pl) # Assume sorted - mask = parent_part1_pl==np.take(part2_pl,lnum2,mode='clip') - lnum2 = lnum2[mask] - part1_in_part2.append(lnum2) - pl1 = np.isin(parent_part1_pl, part2_pl, assume_unique=True) # Assume unique because pr - pl1 = np.where(pl1)[0]+1 - - if pl1.size==0: - part1_pr.append(np.empty(0, dtype=np.int32)) - partial_gnum.append(np.empty(0, dtype=pdm_dtype)) - continue # Pass if no recovering - - vtx_size = PT.Zone.VertexSize(extract_zone) - if vtx_size.size==2: - vtx_size = np.concatenate([vtx_size,np.array([1], dtype=vtx_size.dtype)]) - part1_ijk = s_numbering.index_to_ijk_from_loc(pl1, DIMM_TO_DIMF[mesh_dim][grid_location], vtx_size) - part1_pr.append(np.array([[min(part1_ijk[0]),max(part1_ijk[0])], - [min(part1_ijk[1]),max(part1_ijk[1])], - [min(part1_ijk[2]),max(part1_ijk[2])]])) - - part2_elt_gnum = PT.maia.getGlobalNumbering(part_zone, LOC_TO_GNUM[grid_location])[1] - - partial_gnum.append(as_pdm_gnum(part2_elt_gnum[part2_pl[lnum2]-1])) - else: - part1_in_part2.append(np.empty(0, dtype=np.int32)) - part1_pr.append(np.empty(0, dtype=np.int32)) - partial_gnum.append(np.empty(0, dtype=pdm_dtype)) - - part1_gnum1 = create_sub_numbering(partial_gnum, comm) - - return part1_pr, part1_gnum1, part1_in_part2 diff --git a/maia/algo/part/gcs_ghosts/__init__.py b/maia/algo/part/gcs_ghosts/__init__.py deleted file mode 100644 index 235e93b6..00000000 --- a/maia/algo/part/gcs_ghosts/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from cmaia import part_algo as cpart_algo -from maia.algo.apply_function_to_nodes import apply_to_bases,apply_to_zones - -from maia.utils import require_cpp20 - -@require_cpp20 -def gcs_only_for_ghosts(t): - apply_to_bases(cpart_algo.gcs_only_for_ghosts, t) - -@require_cpp20 -def remove_ghost_info(t): - apply_to_bases(cpart_algo.remove_ghost_info, t) - diff --git a/maia/algo/part/gcs_ghosts/gcs_only_for_ghosts.cpp b/maia/algo/part/gcs_ghosts/gcs_only_for_ghosts.cpp deleted file mode 100644 index 1bf5d8fb..00000000 --- a/maia/algo/part/gcs_ghosts/gcs_only_for_ghosts.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/part/gcs_ghosts/gcs_only_for_ghosts.hpp" -#include "cpp_cgns/cgns.hpp" -#include "cpp_cgns/sids/creation.hpp" -#include "cpp_cgns/sids.hpp" -#include -#include "maia/factory/partitioning/gc_name_convention.hpp" - - -namespace cgns { - - -auto gcs_only_for_ghosts(tree& b) -> void { - auto zs = get_children_by_label(b,"Zone_t"); - for (tree& z : zs) { - auto n_vtx_owned = get_node_value_by_matching(z,":CGNS#LocalNumbering/VertexSizeOwned")[0]; - - auto gcs = get_nodes_by_matching(z,"ZoneGridConnectivity_t/GridConnectivity_t"); - for (tree& gc : gcs) { - if (GridLocation(gc)=="Vertex") { - tree& pl_node = get_child_by_name(gc,"PointList"); - tree& pld_node = get_child_by_name(gc,"PointListDonor"); - - auto old_pl = get_value(pl_node ); - auto old_pld = get_value(pld_node); - - I4 n_old_id = old_pl.size(); - - std::vector new_pl; - std::vector new_pld; - - for (int i=0; i pl_dims = {1,(I8)new_pl.size()}; - value(pl_node) = node_value(std::move(new_pl),pl_dims); - - std::vector pld_dims = {1,(I8)new_pld.size()}; - value(pld_node) = node_value(std::move(new_pld),pld_dims); - } - } - } -} - - -} // cgns -#endif // C++>17 diff --git a/maia/algo/part/gcs_ghosts/gcs_only_for_ghosts.hpp b/maia/algo/part/gcs_ghosts/gcs_only_for_ghosts.hpp deleted file mode 100644 index 013eb320..00000000 --- a/maia/algo/part/gcs_ghosts/gcs_only_for_ghosts.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - - -#include "cpp_cgns/cgns.hpp" - - -namespace cgns { - -auto gcs_only_for_ghosts(tree& b) -> void; - -} // cgns diff --git a/maia/algo/part/geometry/__init__.py b/maia/algo/part/geometry/__init__.py deleted file mode 100644 index b3113367..00000000 --- a/maia/algo/part/geometry/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .centers import compute_cell_center,\ - compute_edge_center,\ - compute_face_center diff --git a/maia/algo/part/geometry/centers.py b/maia/algo/part/geometry/centers.py deleted file mode 100644 index 515e3107..00000000 --- a/maia/algo/part/geometry/centers.py +++ /dev/null @@ -1,134 +0,0 @@ -import numpy as np - -import maia.pytree as PT -from maia.algo.part import connectivity_utils as CU -from maia.utils import np_utils - -import cmaia.part_algo as cpart_algo - -def _mean_coords_from_connectivity(vtx_id_idx, vtx_id, cx, cy, cz): - - vtx_id_n = np.diff(vtx_id_idx) - - mean_x = np.add.reduceat(cx[vtx_id-1], vtx_id_idx[:-1]) / vtx_id_n - mean_y = np.add.reduceat(cy[vtx_id-1], vtx_id_idx[:-1]) / vtx_id_n - mean_z = np.add.reduceat(cz[vtx_id-1], vtx_id_idx[:-1]) / vtx_id_n - - return np_utils.interweave_arrays([mean_x, mean_y, mean_z]) - -@PT.check_is_label("Zone_t") -def compute_cell_center(zone): - """Compute the cell centers of a partitioned zone. - - Input zone must have cartesian coordinates recorded under a unique - GridCoordinates node. - Centers are computed using a basic average over the vertices of the cells. - - Args: - zone (CGNSTree): Partitionned CGNS Zone - Returns: - array: Flat (interlaced) numpy array of cell centers - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #compute_cell_center@start - :end-before: #compute_cell_center@end - :dedent: 2 - """ - cx, cy, cz = PT.Zone.coordinates(zone) - - if PT.Zone.Type(zone) == "Unstructured": - n_cell = PT.Zone.n_cell(zone) - if PT.Zone.has_ngon_elements(zone): - face_vtx_idx, face_vtx, ngon_pe = PT.Zone.ngon_connectivity(zone) - center_cell = cpart_algo.compute_center_cell_u(n_cell, cx, cy, cz, face_vtx, face_vtx_idx, ngon_pe) - else: - cell_vtx_idx, cell_vtx = CU.cell_vtx_connectivity(zone) - center_cell = _mean_coords_from_connectivity(cell_vtx_idx, cell_vtx, cx, cy, cz) - else: - center_cell = cpart_algo.compute_center_cell_s(*PT.Zone.CellSize(zone), cx, cy, cz) - - return center_cell - -@PT.check_is_label("Zone_t") -def compute_face_center(zone): - """Compute the face centers of a partitioned zone. - - Input zone must have cartesian coordinates recorded under a unique - GridCoordinates node. - - Centers are computed using a basic average over the vertices of the faces. - - Note: - If zone is described with standard elements, centers will be computed for elements - explicitly defined in cgns tree. - - Args: - zone (CGNSTree): Partitionned 2D or 3D U CGNS Zone - Returns: - array: Flat (interlaced) numpy array of face centers - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #compute_face_center@start - :end-before: #compute_face_center@end - :dedent: 2 - """ - cx, cy, cz = PT.Zone.coordinates(zone) - - if PT.Zone.Type(zone) == "Unstructured": - if PT.Zone.has_ngon_elements(zone): - ngon_node = PT.Zone.NGonNode(zone) - face_vtx_idx = PT.get_child_from_name(ngon_node, 'ElementStartOffset')[1] - face_vtx = PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1] - else: - face_vtx_idx, face_vtx = CU.cell_vtx_connectivity(zone, dim=2) - return _mean_coords_from_connectivity(face_vtx_idx, face_vtx, cx, cy, cz) - else: - zone_dim = PT.get_value(zone).shape[0] - assert zone_dim >= 2, "1d zones are not managed" - vtx_size = [1,1,1] - vtx_size[:zone_dim] = PT.Zone.VertexSize(zone) - # Create cz if zone_dim == 2 & cz is None - remove_z = False - if zone_dim == 2 and cz is None: - cz = np.zeros(vtx_size, dtype=float, order='F') - remove_z = True - _cx = np.atleast_3d(cx) # Auto expand arrays if zone_dim == 2 - _cy = np.atleast_3d(cy) - _cz = np.atleast_3d(cz) - centers = cpart_algo.compute_center_face_s(*vtx_size, _cx, _cy, _cz) - if remove_z: - centers = np.delete(centers, 3*np.arange(centers.size // 3)+2) - - return centers - -@PT.check_is_label("Zone_t") -def compute_edge_center(zone): - """Compute the edge centers of a partitioned zone. - - Input zone must have cartesian coordinates recorded under a unique - GridCoordinates node, and a unstructured standard elements connectivity. - - Note: - If zone is described with standard elements, centers will be computed for elements - explicitly defined in cgns tree. - - Args: - zone (CGNSTree): Partitionned 2D or 3D U-elts CGNS Zone - Returns: - array: Flat (interlaced) numpy array of edge centers - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #compute_edge_center@start - :end-before: #compute_edge_center@end - :dedent: 2 - """ - cx, cy, cz = PT.Zone.coordinates(zone) - - if PT.Zone.Type(zone) == "Unstructured": - edge_vtx_idx, edge_vtx = CU.cell_vtx_connectivity(zone, dim=1) - return _mean_coords_from_connectivity(edge_vtx_idx, edge_vtx, cx, cy, cz) - else: - raise NotImplementedError("Only U-elts zones are managed") diff --git a/maia/algo/part/geometry/geometry.pybind.cpp b/maia/algo/part/geometry/geometry.pybind.cpp deleted file mode 100644 index 88c4ca76..00000000 --- a/maia/algo/part/geometry/geometry.pybind.cpp +++ /dev/null @@ -1,242 +0,0 @@ -#include -#include -#include -#include -#include -#include "std_e/algorithm/permutation.hpp" -#include "maia/utils/pybind_utils.hpp" - -namespace py = pybind11; - -// -------------------------------------------------------------------- -py::array_t -compute_center_cell_u(int n_cell, - py::array_t& np_cx, - py::array_t& np_cy, - py::array_t& np_cz, - py::array_t& np_face_vtx, - py::array_t& np_face_vtx_idx, - py::array_t& np_parent_elements) -{ - int n_face = np_parent_elements.shape()[0]; - // int n_vtx = np_cx.size(); - // std::cout << "compute_center_cell_u: n_cell = " << n_cell << std::endl; - // std::cout << "compute_center_cell_u: n_face = " << n_face << std::endl; - // std::cout << "compute_center_cell_u: np_face_vtx.size() = " << np_face_vtx.size() << std::endl; - // std::cout << "compute_center_cell_u: np_face_vtx_idx.size() = " << np_face_vtx_idx.size() << std::endl; - // std::cout << "compute_center_cell_u: n_vtx = " << n_vtx << std::endl; - py::array_t np_center_cell(3*n_cell); - std::vector countc(n_cell, 0); - // std::cout << "compute_center_cell_u: countc.size() = " << countc.size() << std::endl; - - auto cx = make_raw_view(np_cx); - auto cy = make_raw_view(np_cy); - auto cz = make_raw_view(np_cz); - auto face_vtx = make_raw_view(np_face_vtx); - auto face_vtx_idx = make_raw_view(np_face_vtx_idx); - auto parent_elements = make_raw_view(np_parent_elements); - auto center_cell = make_raw_view(np_center_cell); - - // Init volume to ZERO - // --------------- - for (int icell = 0; icell < n_cell; ++icell) { - center_cell[3*icell ] = 0.; - center_cell[3*icell+1] = 0.; - center_cell[3*icell+2] = 0.; - countc[icell] = 0; - } - - // Loop over faces - // --------------- - int f_shift(0); // To go back to local cell numbering if ngons are before nface - if (n_face > 0) { - if (std::max(parent_elements[0], parent_elements[0+n_face]) > n_face ) { - f_shift = n_face; - } - } - for (int iface = 0; iface < n_face; ++iface) { - // -> Face -> Cell connectivity - int il = parent_elements[iface ]-1-(f_shift*(parent_elements[iface] > 0)); - int ir = parent_elements[iface+n_face]-1-(f_shift*(parent_elements[iface+n_face] > 0)); - // std::cout << "compute_center_cell_u: iface = " << iface << std::endl; - // std::cout << "compute_center_cell_u: il = " << il << ", ir = " << ir << std::endl; - assert(((il >= -1) && (il < n_cell))); - assert(((ir >= -1) && (ir < n_cell))); - - // Compute the indices of vtx on faces - int begin_vtx = face_vtx_idx[iface ]; - int end_vtx = face_vtx_idx[iface+1]; - // std::cout << "compute_center_cell_u: begin_vtx = " << begin_vtx << ", end_vtx = " << end_vtx << std::endl; - - // Loop over vertex of each face - for (int indvtx = begin_vtx; indvtx < end_vtx; ++indvtx) { - // assert(((indvtx >= 0) && (indvtx < np_face_vtx.size()))); - int ivtx = face_vtx[indvtx]-1; - // assert(((ivtx >= 0) && (ivtx < n_vtx))); - // std::cout << "compute_center_cell_u: ivtx = " << ivtx << ", np_face_vtx.size() = " << np_face_vtx.size() << std::endl; - - if (il >= 0) { - center_cell[3*il ] += cx[ivtx]; - center_cell[3*il+1] += cy[ivtx]; - center_cell[3*il+2] += cz[ivtx]; - countc[il] += 1; - } - - if (ir >= 0) { - center_cell[3*ir ] += cx[ivtx]; - center_cell[3*ir+1] += cy[ivtx]; - center_cell[3*ir+2] += cz[ivtx]; - countc[ir] += 1; - } - } - } - - // Finalize center cell computation - // -------------------------------- - for(int icell = 0; icell < n_cell; ++icell) { - assert(countc[icell] > 0); - center_cell[3*icell ] /= countc[icell]; - center_cell[3*icell+1] /= countc[icell]; - center_cell[3*icell+2] /= countc[icell]; - // std::cout << "compute_center_cell_u: center_cell[3*icell ] = " << center_cell[3*icell ] - // << ", center_cell[3*icell+1] = " << center_cell[3*icell+1] - // << ", center_cell[3*icell+2] = " << center_cell[3*icell+2] << std::endl; - } - - return np_center_cell; -} - -// -------------------------------------------------------------------- -py::array_t -compute_center_cell_s(int nx, int ny, int nz, - py::array_t& np_cx, - py::array_t& np_cy, - py::array_t& np_cz) -{ - - auto cx = np_cx.template mutable_unchecked<3>(); - auto cy = np_cy.template mutable_unchecked<3>(); - auto cz = np_cz.template mutable_unchecked<3>(); - - py::array_t np_center(3*nx*ny*nz); - auto center = make_raw_view(np_center); - - int idx = 0; - for(int k = 0; k < nz; ++k) { - for(int j = 0; j < ny; ++j) { - for(int i = 0; i < nx; ++i) { - center[3*idx+0] = 0.125*(cx(i ,j,k) + cx(i ,j+1,k) + cx(i ,j+1,k+1) + cx(i ,j,k+1) - + cx(i+1,j,k) + cx(i+1,j+1,k) + cx(i+1,j+1,k+1) + cx(i+1,j,k+1)); - center[3*idx+1] = 0.125*(cy(i ,j,k) + cy(i ,j+1,k) + cy(i ,j+1,k+1) + cy(i ,j,k+1) - + cy(i+1,j,k) + cy(i+1,j+1,k) + cy(i+1,j+1,k+1) + cy(i+1,j,k+1)); - center[3*idx+2] = 0.125*(cz(i ,j,k) + cz(i ,j+1,k) + cz(i ,j+1,k+1) + cz(i ,j,k+1) - + cz(i+1,j,k) + cz(i+1,j+1,k) + cz(i+1,j+1,k+1) + cz(i+1,j,k+1)); - idx++; - } - } - } - - return np_center; -} -// -------------------------------------------------------------------- -py::array_t -compute_center_face_s(int nx, int ny, int nz, - py::array_t& np_cx, - py::array_t& np_cy, - py::array_t& np_cz) -{ - - auto cx = np_cx.template mutable_unchecked<3>(); - auto cy = np_cy.template mutable_unchecked<3>(); - auto cz = np_cz.template mutable_unchecked<3>(); - - int n_face_tot = (nz - 1)*(nx - 1)*ny + (nz - 1)*(ny - 1)*nx + nz*(ny - 1)*(nx - 1); // A CHANGER - - py::array_t np_center(3*n_face_tot); - auto center = make_raw_view(np_center); - - int idx = 0; - for(int k = 0; k < nz-1; ++k) { - for(int j = 0; j < ny-1; ++j) { - for(int i = 0; i < nx; ++i) { - center[3*idx+0] = 0.250 * (cx(i, j, k) + cx(i, j+1, k) + cx(i, j, k+1) + cx(i, j+1, k+1)); - center[3*idx+1] = 0.250 * (cy(i, j, k) + cy(i, j+1, k) + cy(i, j, k+1) + cy(i, j+1, k+1)); - center[3*idx+2] = 0.250 * (cz(i, j, k) + cz(i, j+1, k) + cz(i, j, k+1) + cz(i, j+1, k+1)); - idx++; - } - } - }; - for(int k = 0; k < nz-1; ++k) { - for(int j = 0; j < ny; ++j) { - for(int i = 0; i < nx-1; ++i) { - center[3*idx+0] = 0.250 * (cx(i, j, k) + cx(i+1, j, k) + cx(i, j, k+1) + cx(i+1, j, k+1)); - center[3*idx+1] = 0.250 * (cy(i, j, k) + cy(i+1, j, k) + cy(i, j, k+1) + cy(i+1, j, k+1)); - center[3*idx+2] = 0.250 * (cz(i, j, k) + cz(i+1, j, k) + cz(i, j, k+1) + cz(i+1, j, k+1)); - idx++; - } - } - }; - for(int k = 0; k < nz; ++k) { - for(int j = 0; j < ny-1; ++j) { - for(int i = 0; i < nx-1; ++i) { - center[3*idx+0] = 0.250 * (cx(i, j, k) + cx(i+1, j, k) + cx(i, j+1, k) + cx(i+1, j+1, k)); - center[3*idx+1] = 0.250 * (cy(i, j, k) + cy(i+1, j, k) + cy(i, j+1, k) + cy(i+1, j+1, k)); - center[3*idx+2] = 0.250 * (cz(i, j, k) + cz(i+1, j, k) + cz(i, j+1, k) + cz(i+1, j+1, k)); - idx++; - } - } - } - return np_center; -} - -// -------------------------------------------------------------------- -py::array_t -compute_face_normal_u(py::array_t& np_face_vtx_idx, - py::array_t& np_cx, - py::array_t& np_cy, - py::array_t& np_cz) -{ - // Compute face normal ponderated by face area, assuming that coords cx,cy,cz are - // the coordinates of face vertices (with repetitions) - // Eg if we have tri face [3,2,4, 5,4,6] np_cx is [X_3, X_2, X_4, X5,X4,X6] - - auto cx = make_raw_view(np_cx); - auto cy = make_raw_view(np_cy); - auto cz = make_raw_view(np_cz); - auto face_vtx_idx = make_raw_view(np_face_vtx_idx); - - int n_face = np_face_vtx_idx.shape()[0] - 1; - - py::array_t np_face_normal(3*n_face); - auto face_normal = make_raw_view(np_face_normal); - - for (int i = 0; i < n_face; ++i) { - int start = face_vtx_idx[i]; - int end = face_vtx_idx[i+1]; - int n_vtx = end - start; - - double mean_center_x = std::accumulate(&cx[start], &cx[end], 0) / n_vtx; - double mean_center_y = std::accumulate(&cy[start], &cy[end], 0) / n_vtx; - double mean_center_z = std::accumulate(&cz[start], &cz[end], 0) / n_vtx; - - double face_normal_x = 0; - double face_normal_y = 0; - double face_normal_z = 0; - // Compute cross product (uy*vz - uz*vy, uz*vx - ux*vz, ux*vy - uy*vx) - // between OVI, OVI+1 where O is the mean center, and VI / VI+1 are the vertices - for (int j = start; j < end-1; ++j) { - face_normal_x += ((cy[j] - mean_center_y)*(cz[j+1]-mean_center_z) - (cz[j] - mean_center_z)*(cy[j+1]-mean_center_y)); - face_normal_y += ((cz[j] - mean_center_z)*(cx[j+1]-mean_center_x) - (cx[j] - mean_center_x)*(cz[j+1]-mean_center_z)); - face_normal_z += ((cx[j] - mean_center_x)*(cy[j+1]-mean_center_y) - (cy[j] - mean_center_y)*(cx[j+1]-mean_center_x)); - } - face_normal_x += ((cy[end-1] - mean_center_y)*(cz[start]-mean_center_z) - (cz[end-1] - mean_center_z)*(cy[start]-mean_center_y)); - face_normal_y += ((cz[end-1] - mean_center_z)*(cx[start]-mean_center_x) - (cx[end-1] - mean_center_x)*(cz[start]-mean_center_z)); - face_normal_z += ((cx[end-1] - mean_center_x)*(cy[start]-mean_center_y) - (cy[end-1] - mean_center_y)*(cx[start]-mean_center_x)); - - face_normal[3*i+0] = 0.5*face_normal_x; - face_normal[3*i+1] = 0.5*face_normal_y; - face_normal[3*i+2] = 0.5*face_normal_z; - } - - return np_face_normal; -} diff --git a/maia/algo/part/geometry/geometry.pybind.hpp b/maia/algo/part/geometry/geometry.pybind.hpp deleted file mode 100644 index 600230bb..00000000 --- a/maia/algo/part/geometry/geometry.pybind.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include - -// -------------------------------------------------------------------- -pybind11::array_t -compute_center_cell_u(int n_cell, - pybind11::array_t& np_cx, - pybind11::array_t& np_cy, - pybind11::array_t& np_cz, - pybind11::array_t& np_face_vtx, - pybind11::array_t& np_face_vtx_idx, - pybind11::array_t& np_parent_elements); - -// -------------------------------------------------------------------- -pybind11::array_t -compute_center_cell_s(int nx, int ny, int nz, - pybind11::array_t& np_cx, - pybind11::array_t& np_cy, - pybind11::array_t& np_cz); - -// -------------------------------------------------------------------- -pybind11::array_t -compute_center_face_s(int nx, int ny, int nz, - pybind11::array_t& np_cx, - pybind11::array_t& np_cy, - pybind11::array_t& np_cz); - -// -------------------------------------------------------------------- -pybind11::array_t -compute_face_normal_u(pybind11::array_t& np_face_vtx_idx, - pybind11::array_t& np_cx, - pybind11::array_t& np_cy, - pybind11::array_t& np_cz); diff --git a/maia/algo/part/geometry/test/test_centers.py b/maia/algo/part/geometry/test/test_centers.py deleted file mode 100644 index 4e6f143d..00000000 --- a/maia/algo/part/geometry/test/test_centers.py +++ /dev/null @@ -1,159 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia -import maia.pytree as PT -from maia.factory.dcube_generator import dcube_generate, dcube_nodal_generate - -from maia.algo.part.geometry import centers - -@pytest_parallel.mark.parallel(1) -def test_compute_cell_center(comm): - #Test U - tree = dcube_generate(3, 1., [0,0,0], comm) - zoneU = PT.get_all_Zone_t(tree)[0] - #On partitions, element are supposed to be I4 - for elt_node in PT.iter_children_from_label(zoneU, 'Elements_t'): - for name in ['ElementConnectivity', 'ParentElements', 'ElementStartOffset']: - node = PT.get_child_from_name(elt_node, name) - node[1] = node[1].astype(np.int32) - - cell_center = centers.compute_cell_center(zoneU) - expected_cell_center = np.array([0.25, 0.25, 0.25, - 0.75, 0.25, 0.25, - 0.25, 0.75, 0.25, - 0.75, 0.75, 0.25, - 0.25, 0.25, 0.75, - 0.75, 0.25, 0.75, - 0.25, 0.75, 0.75, - 0.75, 0.75, 0.75]) - assert (cell_center == expected_cell_center).all() - - #Test S - cx_s = PT.get_node_from_name(zoneU, 'CoordinateX')[1].reshape((3,3,3), order='F') - cy_s = PT.get_node_from_name(zoneU, 'CoordinateY')[1].reshape((3,3,3), order='F') - cz_s = PT.get_node_from_name(zoneU, 'CoordinateZ')[1].reshape((3,3,3), order='F') - - zoneS = PT.new_Zone(size=[[3,2,0], [3,2,0], [3,2,0]], type='Structured') - grid_coords = PT.new_GridCoordinates(parent=zoneS) - PT.new_DataArray('CoordinateX', cx_s, parent=grid_coords) - PT.new_DataArray('CoordinateY', cy_s, parent=grid_coords) - PT.new_DataArray('CoordinateZ', cz_s, parent=grid_coords) - cell_center = centers.compute_cell_center(zoneS) - assert (cell_center == expected_cell_center).all() - - #Test Elts - tree = maia.factory.generate_dist_block(3, 'HEXA_8', comm) - zoneU = PT.get_all_Zone_t(tree)[0] - #On partitions, element are supposed to be I4 - for elt_node in PT.iter_children_from_label(zoneU, 'Elements_t'): - for name in ['ElementConnectivity']: - node = PT.get_child_from_name(elt_node, name) - node[1] = node[1].astype(np.int32) - cell_center = centers.compute_cell_center(zoneU) - assert (cell_center == expected_cell_center).all() - -@pytest_parallel.mark.parallel(1) -def test_compute_face_center_3d(comm): - tree = dcube_generate(3, 1., [0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - - expected = np.array([ - 0.25,0.25,0. , 0.75,0.25,0. , 0.25,0.75,0. , 0.75,0.75,0. , - 0.25,0.25,0.5 , 0.75,0.25,0.5 , 0.25,0.75,0.5 , 0.75,0.75,0.5 , - 0.25,0.25,1. , 0.75,0.25,1. , 0.25,0.75,1. , 0.75,0.75,1. , - 0. ,0.25,0.25, 0. ,0.75,0.25, 0. ,0.25,0.75, 0. ,0.75,0.75, - 0.5 ,0.25,0.25, 0.5 ,0.75,0.25, 0.5 ,0.25,0.75, 0.5 ,0.75,0.75, - 1. ,0.25,0.25, 1. ,0.75,0.25, 1. ,0.25,0.75, 1. ,.75 ,.75 , - 0.25,0. ,0.25, 0.25,0. ,0.75, 0.75,0. ,0.25, 0.75,0. ,0.75, - 0.25,0.5 ,0.25, 0.25,0.5 ,0.75, 0.75,0.5 ,0.25, 0.75,0.5 ,0.75, - 0.25,1. ,0.25, 0.25,1. ,0.75, 0.75,1. ,0.25, 0.75,1. ,0.75,]) - - assert np.array_equal(centers.compute_face_center(zone), expected) - - tree = maia.factory.generate_dist_block(3, 'Structured', comm) - # Reput coords as partitioned tree - tree = maia.factory.partition_dist_tree(tree, comm) - zone = PT.get_all_Zone_t(tree)[0] - expected = np.array([ - 0. ,0.25,0.25, 0.5 ,0.25,0.25, 1. ,0.25,0.25, - 0. ,0.75,0.25, 0.5 ,0.75,0.25, 1. ,0.75,0.25, - 0. ,0.25,0.75, 0.5 ,0.25,0.75, 1. ,0.25,0.75, - 0. ,0.75,0.75, 0.5 ,0.75,0.75, 1. ,0.75,0.75, # End of IFaces - 0.25,0. ,0.25, 0.75,0. ,0.25, 0.25,0.5 ,0.25, - 0.75,0.5 ,0.25, 0.25,1. ,0.25, 0.75,1. ,0.25, - 0.25,0. ,0.75, 0.75,0. ,0.75, 0.25,0.5 ,0.75, - 0.75,0.5 ,0.75, 0.25,1. ,0.75, 0.75,1. ,0.75, # End of JFaces - 0.25,0.25,0. , 0.75,0.25,0. , 0.25,0.75,0. , - 0.75,0.75,0. , 0.25,0.25,0.5 , 0.75,0.25,0.5 , - 0.25,0.75,0.5 , 0.75,0.75,0.5 , 0.25,0.25,1. , - 0.75,0.25,1. , 0.25,0.75,1. , 0.75,0.75,1. ]) # End of KFaces - assert np.array_equal(centers.compute_face_center(zone), expected) - -@pytest.mark.skipif(not maia.pdma_enabled, reason="Require ParaDiGMA") -@pytest_parallel.mark.parallel(1) -def test_compute_face_center_2d(comm): - dslice_tree = maia.factory.generate_dist_block(4, "QUAD_4", comm) - pslice_tree = maia.factory.partition_dist_tree(dslice_tree, comm) - zone = PT.get_all_Zone_t(pslice_tree)[0] - - expected = np.array([0.16, 0.16, 0., 0.83, 0.50, 0., 0.50, 0.83, 0., - 0.16, 0.83, 0., 0.50, 0.50, 0., 0.50, 0.16, 0., - 0.16, 0.50, 0., 0.83, 0.16, 0., 0.83, 0.83, 0., ]) - - assert np.allclose(centers.compute_face_center(zone), expected, atol=1e-2) - -def test_compute_face_center_2d_S(comm): - # With CZ - - tree = maia.factory.generate_dist_block([3,3,1], 'Structured', comm) - # As partitioned - for dir in ['X', 'Y']: - node = PT.get_node_from_name(tree, f'Coordinate{dir}') - node[1] = node[1].reshape((3,3), order='F') - # Change Z for test - node = PT.get_node_from_name(tree, 'CoordinateZ') - node[1] = np.array([[0,0.1,0], [0,0,0], [0,0.2,0.2]], order='F') - zone = PT.get_all_Zone_t(tree)[0] - expected = np.array([0.25,0.25,0.025 , 0.75 ,0.25,0.05, 0.25, 0.75, 0.025, 0.75, 0.75, 0.1]) - assert np.array_equal(centers.compute_face_center(zone), expected) - - # Without CZ - tree = maia.factory.generate_dist_block([3,3], 'Structured', comm, origin=[0., 0.]) - # As partitioned - for dir in ['X', 'Y']: - node = PT.get_node_from_name(tree, f'Coordinate{dir}') - node[1] = node[1].reshape((3,3), order='F') - zone = PT.get_all_Zone_t(tree)[0] - expected = np.array([0.25,0.25, 0.75 ,0.25, 0.25, 0.75, 0.75, 0.75]) - assert np.array_equal(centers.compute_face_center(zone), expected) - -@pytest.mark.skipif(not maia.pdm_has_ptscotch, reason="Require PTScotch") -@pytest_parallel.mark.parallel(1) -def test_compute_face_center_elmts_3d(comm): - tree = dcube_nodal_generate(2, 1., [0,0,0], 'HEXA_8', comm) - - part_tree = maia.factory.partition_dist_tree(tree, comm, graph_part_tool='ptscotch') - # Nb : metis is not robust if n_cell == 1 - zone = PT.get_all_Zone_t(part_tree)[0] - - expected = np.array([0.5,0.5,0., 0.5,0.5,1., 0.,0.5,0.5, - 1.,0.5,0.5, 0.5,0.,0.5, 0.5,1.,0.5]) - assert np.allclose(centers.compute_face_center(zone), expected, atol=1e-2) - -@pytest.mark.parametrize("elt_kind", ["QUAD_4" ,'NFACE_n']) -@pytest_parallel.mark.parallel(1) -def test_compute_edge_center_2d(elt_kind, comm): - tree = maia.factory.generate_dist_block(3, elt_kind, comm) - zone = PT.get_all_Zone_t(tree)[0] - PT.rm_nodes_from_name(zone, ":CGNS#Distribution") # Fake part_zone (from test_connectivity_utils) - - if elt_kind=="QUAD_4": - expected = np.array([0.25,0.,0., 0.75,0.,0., 0.25,1.,0., 0.75,1.,0., - 0.,0.25,0., 0.,0.75,0., 1.,0.25,0., 1.,0.75,0.]) - assert np.allclose(centers.compute_edge_center(zone), expected, atol=1e-2) - - elif elt_kind=="NFACE_n": - with pytest.raises(NotImplementedError): - centers.compute_edge_center(zone) diff --git a/maia/algo/part/interpolate.py b/maia/algo/part/interpolate.py deleted file mode 100644 index b1c22dae..00000000 --- a/maia/algo/part/interpolate.py +++ /dev/null @@ -1,285 +0,0 @@ -from mpi4py import MPI -import numpy as np - -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import py_utils, np_utils -from maia.utils import logging as mlog -from maia.transfer import utils as te_utils -from maia.factory.dist_from_part import get_parts_per_blocks - -from .import point_cloud_utils as PCU -from .import multidom_gnum as MDG -from .import localize as LOC -from .import closest_points as CLO - -class Interpolator: - """ Low level class to perform interpolations """ - def __init__(self, src_parts_per_dom, tgt_parts_per_dom, src_to_tgt, input_loc, output_loc, comm): - self.src_parts = py_utils.to_flat_list(src_parts_per_dom) - self.tgt_parts = py_utils.to_flat_list(tgt_parts_per_dom) - - _, src_lngn_per_dom = MDG.get_shifted_ln_to_gn_from_loc(src_parts_per_dom, input_loc, comm) - all_src_lngn = py_utils.to_flat_list(src_lngn_per_dom) - - _, tgt_lngn_per_dom = MDG.get_shifted_ln_to_gn_from_loc(tgt_parts_per_dom, output_loc, comm) - all_tgt_lngn = py_utils.to_flat_list(tgt_lngn_per_dom) - - _src_to_tgt_idx = [data['target_idx'] for data in src_to_tgt] - _src_to_tgt = [data['target'] for data in src_to_tgt] - self.PTP = PDM.PartToPart(comm, - all_src_lngn, - all_tgt_lngn, - _src_to_tgt_idx, - _src_to_tgt) - - self.referenced_nums = self.PTP.get_referenced_lnum2() - self.sending_gnums = self.PTP.get_gnum1_come_from() - self.output_loc = output_loc - self.input_loc = input_loc - - # Send distances to targets partitions (if available) - try: - _dist = [data['dist2'] for data in src_to_tgt] - request = self.PTP.iexch(PDM._PDM_MPI_COMM_KIND_P2P, - PDM._PDM_PART_TO_PART_DATA_DEF_ORDER_PART1_TO_PART2, - _dist) - _, self.tgt_dist = self.PTP.wait(request) - except KeyError: - pass - - - def _reduce_single_val(self, i_part, data): - """ - A basic reduce function who take the first received value for each target - """ - come_from_idx = self.sending_gnums[i_part]['come_from_idx'] - assert (np.diff(come_from_idx) == 1).all() - return data - - def _reduce_mean_dist(self, i_part, data): - """ - Compute a weighted mean of the received values. Weights are the inverse of the squared distance from each source. - Usable only if distance are available in src_to_tgt dict (eg if closestpoint method was used). - """ - come_from_idx = self.sending_gnums[i_part]['come_from_idx'] - dist_threshold = np.maximum(self.tgt_dist[i_part], 1E-20) - reduced_data = np.add.reduceat(data/dist_threshold, come_from_idx[:-1]) - reduced_factor = np.add.reduceat(1/dist_threshold, come_from_idx[:-1]) - assert reduced_data.size == come_from_idx.size - 1 - return reduced_data / reduced_factor - - - def exchange_fields(self, container_name, reduce_func=_reduce_single_val): - """ - For all fields found under container_name node, - - Perform a part to part exchange - - Reduce the received data using reduce_func (because tgt elements can receive multiple data) - - Fill the target sol with a default value + the reduced value - """ - - #Check that solutions are known on each source partition - fields_per_part = list() - for src_part in self.src_parts: - container = PT.get_node_from_path(src_part, container_name) - assert PT.Subset.GridLocation(container) == self.input_loc - fields_name = sorted([PT.get_name(array) for array in PT.iter_children_from_label(container, 'DataArray_t')]) - fields_per_part.append(fields_name) - assert fields_per_part.count(fields_per_part[0]) == len(fields_per_part) - - #Cleanup target partitions - for tgt_part in self.tgt_parts: - PT.rm_children_from_name(tgt_part, container_name) - fs = PT.new_FlowSolution(container_name, loc=self.output_loc, parent=tgt_part) - - #Collect src sol - src_field_dic = dict() - for field_name in fields_per_part[0]: - field_path = container_name + '/' + field_name - src_field_dic[field_name] = [PT.get_node_from_path(part, field_path)[1] for part in self.src_parts] - - #Exchange - for field_name, src_sol in src_field_dic.items(): - request = self.PTP.iexch(PDM._PDM_MPI_COMM_KIND_P2P, - PDM._PDM_PART_TO_PART_DATA_DEF_ORDER_PART1, - src_sol) - strides, lnp_part_data = self.PTP.wait(request) - - for i_part, tgt_part in enumerate(self.tgt_parts): - fs = PT.get_node_from_path(tgt_part, container_name) - data_size = PT.Zone.n_cell(tgt_part) if self.output_loc == 'CellCenter' else PT.Zone.n_vtx(tgt_part) - data = np.nan * np.ones(data_size) - reduced_data = reduce_func(self, i_part, lnp_part_data[i_part]) - data[self.referenced_nums[i_part]-1] = reduced_data #Use referenced ids to erase default value - if PT.Zone.Type(tgt_part) == 'Unstructured': - PT.update_child(fs, field_name, 'DataArray_t', data) - else: - shape = PT.Zone.CellSize(tgt_part) if self.output_loc == 'CellCenter' else PT.Zone.VertexSize(tgt_part) - PT.update_child(fs, field_name, 'DataArray_t', data.reshape(shape, order='F')) - - -def create_src_to_tgt(src_parts_per_dom, - tgt_parts_per_dom, - comm, - src_loc = 'CellCenter', - tgt_loc = 'CellCenter', - strategy = 'Closest', - loc_tolerance = 1E-6, - n_closest_pt = 1): - """ Create a source to target indirection depending of the choosen strategy. - - This indirection can then be used to create an interpolator object. - """ - - assert strategy in ['LocationAndClosest', 'Location', 'Closest'] - - #Phase 1 -- localisation - if strategy != 'Closest': - if src_loc != 'CellCenter': - raise NotImplementedError("For vertex-located fields, only 'Closest' strategy is implemented") - location_out, location_out_inv = LOC._localize_points(src_parts_per_dom, tgt_parts_per_dom, \ - tgt_loc, comm, True, loc_tolerance) - - # output is nested by domain so we need to flatten it - all_unlocated = [data['unlocated_ids'] for domain in location_out for data in domain] - all_located_inv = py_utils.to_flat_list(location_out_inv) - n_unlocated = sum([t.size for t in all_unlocated]) - n_tot_unlocated = comm.allreduce(n_unlocated, op=MPI.SUM) - if comm.Get_rank() == 0: - mlog.stat(f"[interpolation] Number of unlocated points for Location method is {n_tot_unlocated}") - - - all_closest_inv = list() - if strategy == 'Closest' or (strategy == 'LocationAndClosest' and n_tot_unlocated > 0): - - # > Setup source for closest point (with shift to manage multidomain) - _, src_clouds = PCU.get_shifted_point_clouds(src_parts_per_dom, src_loc, comm) - src_clouds = py_utils.to_flat_list(src_clouds) - - # > Setup target for closest point (with shift to manage multidomain) - _, tgt_clouds = PCU.get_shifted_point_clouds(tgt_parts_per_dom, tgt_loc, comm) - tgt_clouds = py_utils.to_flat_list(tgt_clouds) - - # > If we previously did a mesh location, we only treat unlocated points : create a sub global numbering - if strategy != 'Closest': - assert len(all_unlocated) == len(tgt_clouds) - sub_clouds = [PCU.extract_sub_cloud(*tgt_cloud, all_unlocated[i]) for i,tgt_cloud in enumerate(tgt_clouds)] - all_extracted_lngn = [sub_cloud[1] for sub_cloud in sub_clouds] - all_sub_lngn = PCU.create_sub_numbering(all_extracted_lngn, comm) #This one is collective - tgt_clouds = [(tgt_cloud[0], sub_lngn) for tgt_cloud, sub_lngn in zip(sub_clouds, all_sub_lngn)] - - n_clo = n_closest_pt if strategy == 'Closest' else 1 - all_closest, all_closest_inv = CLO._closest_points(src_clouds, tgt_clouds, comm, n_clo, reverse=True) - - #If we worked on sub gnum, we must go back to original numbering - if strategy != 'Closest': - gnum_to_transform = [results["tgt_in_src"] for results in all_closest_inv] - PDM.transform_to_parent_gnum(gnum_to_transform, all_sub_lngn, all_extracted_lngn, comm) - - # Combine Location & Closest results if both method were used - if strategy == 'Location' or (strategy == 'LocationAndClosest' and n_tot_unlocated == 0): - src_to_tgt = [{'target_idx' : data['elt_pts_inside_idx'], - 'target' : data['points_gnum_shifted']} for data in all_located_inv] - elif strategy == 'Closest': - src_to_tgt = [{'target_idx' : data['tgt_in_src_idx'], - 'target' : data['tgt_in_src'], - 'dist2' : data['tgt_in_src_dist2']} for data in all_closest_inv] - else: - src_to_tgt = [] - for res_loc, res_clo in zip(all_located_inv, all_closest_inv): - tgt_in_src_idx, tgt_in_src = np_utils.jagged_merge(res_loc['elt_pts_inside_idx'], res_loc['points_gnum_shifted'], \ - res_clo['tgt_in_src_idx'], res_clo['tgt_in_src']) - src_to_tgt.append({'target_idx' :tgt_in_src_idx, 'target' :tgt_in_src}) - - return src_to_tgt - - - -def interpolate_from_parts_per_dom(src_parts_per_dom, tgt_parts_per_dom, comm, containers_name, location, **options): - """ - Low level interface for interpolation - Input are a list of partitioned zones for each src domain, and a list of partitioned zone for each tgt - domain. Lists mush be coherent across procs, ie we must have an empty entry if a proc does not know a domain. - - containers_name is the list of FlowSolution containers to be interpolated - location is the output location (CellCenter or Vertex); input location can be Vertex only if - strategy is 'Closest', otherwise it must be CellCenter - **options are passed to interpolator creationg function, see create_src_to_tgt - """ - # Guess location of input fields - if len(containers_name) == 0: - return - try: - first_part = next(part for dom in src_parts_per_dom for part in dom) - input_loc = PT.Subset.GridLocation(PT.get_child_from_name(first_part, containers_name[0])) - except StopIteration: - input_loc = '' - input_loc = comm.allreduce(input_loc, op=MPI.MAX) - - src_to_tgt = create_src_to_tgt(src_parts_per_dom, tgt_parts_per_dom, comm, input_loc, location, **options) - - interpolator = Interpolator(src_parts_per_dom, tgt_parts_per_dom, src_to_tgt, input_loc, location, comm) - for container_name in containers_name: - interpolator.exchange_fields(container_name) - -def interpolate_from_part_trees(src_tree, tgt_tree, comm, containers_name, location, **options): - """Interpolate fields between two partitionned trees. - - For now, interpolation is limited to lowest order: target points take the value of the - closest point (or their englobing cell, depending of choosed options) in the source mesh. - Interpolation strategy can be controled thought the options kwargs: - - - ``strategy`` (default = 'Closest') -- control interpolation method - - - 'Closest' : Target points take the value of the closest source cell center. - - 'Location' : Target points take the value of the cell in which they are located. - Unlocated points have take a ``NaN`` value. - - 'LocationAndClosest' : Use 'Location' method and then 'ClosestPoint' method - for the unlocated points. - - - ``loc_tolerance`` (default = 1E-6) -- Geometric tolerance for Location method. - - Important: - If ``strategy`` is not 'Closest', source tree must have an unstructured-NGON - connectivity and CellCenter located fields. - - See also: - :func:`create_interpolator_from_part_trees` takes the same parameters (excepted ``containers_name``, - which must be replaced by ``src_location``), and returns an Interpolator object which can be used - to exchange containers more than once through its ``Interpolator.exchange_fields(container_name)`` method. - - Args: - src_tree (CGNSTree): Source tree, partitionned. Only U-NGon connectivities are managed. - tgt_tree (CGNSTree): Target tree, partitionned. Structured or U-NGon connectivities are managed. - comm (MPIComm): MPI communicator - containers_name (list of str) : List of the names of the source FlowSolution_t nodes to transfer. - location ({'CellCenter', 'Vertex'}) : Expected target location of the fields. - **options: Options related to interpolation strategy - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #interpolate_from_part_trees@start - :end-before: #interpolate_from_part_trees@end - :dedent: 2 - """ - src_parts_per_dom = list(get_parts_per_blocks(src_tree, comm).values()) - tgt_parts_per_dom = list(get_parts_per_blocks(tgt_tree, comm).values()) - - interpolate_from_parts_per_dom(src_parts_per_dom, tgt_parts_per_dom, comm, containers_name, location, **options) - - -def create_interpolator_from_part_trees(src_tree, tgt_tree, comm, src_location, location, **options): - """Same as interpolate_from_part_trees, but return the interpolator object instead - of doing interpolations. Interpolator can be called multiple time to exchange - fields without recomputing the src_to_tgt indirection (geometry must remain the same). - """ - src_parts_per_dom = list(get_parts_per_blocks(src_tree, comm).values()) - tgt_parts_per_dom = list(get_parts_per_blocks(tgt_tree, comm).values()) - - src_to_tgt = create_src_to_tgt(src_parts_per_dom, tgt_parts_per_dom, comm, src_location, location, **options) - return Interpolator(src_parts_per_dom, tgt_parts_per_dom, src_to_tgt, src_location, location, comm) - - diff --git a/maia/algo/part/isosurf.py b/maia/algo/part/isosurf.py deleted file mode 100644 index c5eeda43..00000000 --- a/maia/algo/part/isosurf.py +++ /dev/null @@ -1,664 +0,0 @@ -import time -import mpi4py.MPI as MPI -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT -import maia.utils.logging as mlog - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.transfer import utils as TEU -from maia.factory import dist_from_part -from maia.utils import np_utils, layouts, py_utils -from .extraction_utils import local_pl_offset, LOC_TO_DIM, get_partial_container_stride_and_order -from .point_cloud_utils import create_sub_numbering - -import Pypdm.Pypdm as PDM - -familyname_query = lambda n: PT.get_label(n) in ['FamilyName_t', 'AdditionalFamilyName_t'] - -def copy_referenced_families(source_base, target_base): - """ Copy from source_base to target_base the Family_t nodes referenced - by a (Additional)FamilyName (at zone level) in the target base """ - copied_families = [] - for fam_node in PT.get_children_from_predicates(target_base, ['Zone_t', familyname_query]): - fam_name = PT.get_value(fam_node) - if fam_name not in copied_families: - copied_families.append(fam_name) - family_node = PT.get_child_from_predicate(source_base, fam_name) - PT.add_child(target_base, family_node) - - -def exchange_field_one_domain(part_zones, iso_part_zone, containers_name, comm): - - for container_name in containers_name : - - # > Retrieve fields name + GridLocation + PointList if container - # is not know by every partition - mask_zone = ['MaskedZone', None, [], 'Zone_t'] - dist_from_part.discover_nodes_from_matching(mask_zone, part_zones, container_name, comm, \ - child_list=['GridLocation', 'BCRegionName', 'GridConnectivityRegionName']) - - fields_query = lambda n: PT.get_label(n) in ['DataArray_t', 'IndexArray_t'] - dist_from_part.discover_nodes_from_matching(mask_zone, part_zones, [container_name, fields_query], comm) - mask_container = PT.get_child_from_name(mask_zone, container_name) - if mask_container is None: - raise ValueError("[maia-isosurfaces] asked container for exchange is not in tree") - - # > Manage BC and GC ZSR - ref_zsr_node = mask_container - bc_descriptor_n = PT.get_child_from_name(mask_container, 'BCRegionName') - gc_descriptor_n = PT.get_child_from_name(mask_container, 'GridConnectivityRegionName') - assert not (bc_descriptor_n and gc_descriptor_n) - if bc_descriptor_n is not None: - bc_name = PT.get_value(bc_descriptor_n) - dist_from_part.discover_nodes_from_matching(mask_zone, part_zones, ['ZoneBC_t', bc_name], comm, child_list=['PointList', 'GridLocation_t']) - ref_zsr_node = PT.get_child_from_predicates(mask_zone, f'ZoneBC_t/{bc_name}') - elif gc_descriptor_n is not None: - gc_name = PT.get_value(gc_descriptor_n) - dist_from_part.discover_nodes_from_matching(mask_zone, part_zones, ['ZoneGridConnectivity_t', gc_name], comm, child_list=['PointList', 'GridLocation_t']) - ref_zsr_node = PT.get_child_from_predicates(mask_zone, f'ZoneGridConnectivity_t/{gc_name})') - - gridLocation = PT.Subset.GridLocation(ref_zsr_node) - partial_field = PT.get_child_from_name(ref_zsr_node, 'PointList') is not None - assert gridLocation in ['Vertex', 'FaceCenter', 'CellCenter'] - - - # > Part1 (ISOSURF) objects definition - # LN_TO_GN - _gridLocation = {"Vertex" : "Vertex", "FaceCenter" : "Element", "CellCenter" : "Cell"} - - create_fs = True - if iso_part_zone is not None: - elt_n = iso_part_zone if gridLocation!='FaceCenter' else PT.get_child_from_name(iso_part_zone, 'BAR_2') - - create_fs = gridLocation!='FaceCenter' or \ - ( gridLocation=='FaceCenter' and PT.get_child_from_name(iso_part_zone, 'BAR_2') is not None) - - if elt_n is not None : - part1_elt_gnum_n = PT.maia.getGlobalNumbering(elt_n, _gridLocation[gridLocation]) - part1_ln_to_gn = [PT.get_value(part1_elt_gnum_n)] - else : - part1_elt_gnum_n = None - part1_ln_to_gn = [] - - # > Link between part1 and part2 - part1_maia_iso_zone = PT.get_child_from_name(iso_part_zone, "maia#surface_data") - if gridLocation=='Vertex' : - part1_weight = [PT.get_child_from_name(part1_maia_iso_zone, "Vtx_parent_weight" )[1]] - part1_to_part2 = [PT.get_child_from_name(part1_maia_iso_zone, "Vtx_parent_gnum" )[1]] - part1_to_part2_idx = [PT.get_child_from_name(part1_maia_iso_zone, "Vtx_parent_idx" )[1]] - if gridLocation=='FaceCenter' : - # Output should be edge located so check if iso surface locally has edge - part1_to_part2 = [PT.get_child_from_name(part1_maia_iso_zone, "Face_parent_bnd_edges")[1]] if elt_n is not None else [] - part1_to_part2_idx = [np.arange(0, PT.get_value(part1_elt_gnum_n).size+1, dtype=np.int32)] if elt_n is not None else [] - if gridLocation=='CellCenter' : - part1_to_part2 = [PT.get_child_from_name(part1_maia_iso_zone, "Cell_parent_gnum")[1]] - part1_to_part2_idx = [np.arange(0, PT.get_value(part1_elt_gnum_n).size+1, dtype=np.int32)] - - if iso_part_zone is None: - part1_ln_to_gn = [] - part1_to_part2 = [] - part1_to_part2_idx = [] - - - # > Part2 (VOLUME) objects definition - part2_ln_to_gn = list() - for part_zone in part_zones: - elt_n = part_zone if gridLocation!='FaceCenter' else PT.get_child_from_name(part_zone, 'NGonElements') - part2_elt_gnum_n = PT.maia.getGlobalNumbering(elt_n, _gridLocation[gridLocation]) - part2_ln_to_gn.append(PT.get_value(part2_elt_gnum_n)) - - - # > P2P Object - ptp = PDM.PartToPart(comm, - part1_ln_to_gn, - part2_ln_to_gn, - part1_to_part2_idx, - part1_to_part2 ) - - - # > FlowSolution node def in isosurf zone - fs_loc = gridLocation if gridLocation!="FaceCenter" else "EdgeCenter" - if iso_part_zone is not None and create_fs: - FS_iso = PT.new_FlowSolution(container_name, loc=fs_loc, parent=iso_part_zone) - else : - FS_iso = None # Beware to the loop on containers_name (FS_iso could have been initialised with previous container_name) - - if partial_field: - pl_gnum1, stride = get_partial_container_stride_and_order(part_zones, container_name, gridLocation, ptp, comm) - - # > Field exchange - for fld_node in PT.get_children_from_label(mask_container, 'DataArray_t'): - fld_name = PT.get_name(fld_node) - fld_path = f"{container_name}/{fld_name}" - - if partial_field: - # Get field and organize it according to the gnum1_come_from arrays order - fld_data = list() - for i_part, part_zone in enumerate(part_zones) : - fld_n = PT.get_node_from_path(part_zone,fld_path) - fld_data_tmp = PT.get_value(fld_n) if fld_n is not None else np.empty(0, dtype=np.float64) - fld_data.append(fld_data_tmp[pl_gnum1[i_part]]) - p2p_type = PDM._PDM_PART_TO_PART_DATA_DEF_ORDER_GNUM1_COME_FROM - - else : - fld_data = [PT.get_node_from_path(part_zone,fld_path)[1] for part_zone in part_zones] - stride = 1 - p2p_type = PDM._PDM_PART_TO_PART_DATA_DEF_ORDER_PART2 - - # Reverse iexch - req_id = ptp.reverse_iexch(PDM._PDM_MPI_COMM_KIND_P2P, - p2p_type, - fld_data, - part2_stride=stride) - part1_stride, part1_data = ptp.reverse_wait(req_id) - - # > Placement - if iso_part_zone is not None and create_fs: - i_part = 0 # One isosurface partition - # Ponderation if vertex - if gridLocation=="Vertex" : - weighted_fld = part1_data[i_part]*part1_weight[i_part] - part1_data[i_part] = np.add.reduceat(weighted_fld, part1_to_part2_idx[i_part][:-1]) - if part1_data[i_part].size!=0: - PT.new_DataArray(fld_name, part1_data[i_part], parent=FS_iso) - - # Build PL with the last exchange stride - if partial_field: - if len(part1_data)!=0 and part1_data[0].size!=0: - new_point_list = np.where(part1_stride[0]==1)[0] - point_list = new_point_list + local_pl_offset(iso_part_zone, LOC_TO_DIM[gridLocation]-1)+1 - new_pl_node = PT.new_IndexArray(name='PointList', value=point_list.reshape((1,-1), order='F'), parent=FS_iso) - partial_part1_lngn = [part1_ln_to_gn[0][new_point_list]] - else: - partial_part1_lngn = [] - - # Update global numbering in FS - partial_gnum = create_sub_numbering(partial_part1_lngn, comm) - if iso_part_zone is not None and create_fs and len(partial_gnum)!=0: - PT.maia.newGlobalNumbering({'Index' : partial_gnum[0]}, parent=FS_iso) - - # Remove node if is empty - if FS_iso is not None and len(PT.get_children_from_label(FS_iso, 'DataArray_t'))==0: - PT.rm_child(iso_part_zone, FS_iso) - - -def _exchange_field(part_tree, iso_part_tree, containers_name, comm) : - """ - Exchange fields found under each container from part_tree to iso_part_tree - """ - # Get zones by domains - part_tree_per_dom = dist_from_part.get_parts_per_blocks(part_tree, comm) - - # Loop over domains - for domain_path, part_zones in part_tree_per_dom.items(): - # Get zone from isosurf (one zone by domain) - iso_part_zones = TEU.get_partitioned_zones(iso_part_tree, f"{domain_path}") - iso_part_zone = iso_part_zones[0] if len(iso_part_zones)!=0 else None - exchange_field_one_domain(part_zones, iso_part_zone, containers_name, comm) - - - -def iso_surface_one_domain(part_zones, iso_kind, iso_params, elt_type, graph_part_tool, comm): - """ - Compute isosurface in a zone - """ - _KIND_TO_SET_FUNC = {"PLANE" : PDM.IsoSurface.plane_equation_set, - "SPHERE" : PDM.IsoSurface.sphere_equation_set, - "ELLIPSE" : PDM.IsoSurface.ellipse_equation_set, - "QUADRIC" : PDM.IsoSurface.quadric_equation_set} - - PDM_iso_type = eval(f"PDM._PDM_ISO_SURFACE_KIND_{iso_kind}") - PDM_elt_type = PT.maia.pdm_elts.cgns_elt_name_to_pdm_element_type(elt_type) - - if iso_kind=="FIELD" : - assert isinstance(iso_params, list) and len(iso_params) == len(part_zones) - - - n_part = len(part_zones) - - # Definition of the PDM object IsoSurface - pdm_isos = PDM.IsoSurface(comm, 3, PDM_iso_type, n_part) - pdm_isos.isosurf_elt_type_set(PDM_elt_type) - # > HILBERT : partitioning can be desequilibrated in some 2D case (but parallelism independant) - pdm_isos.isosurf_part_method_set(eval(f"PDM._PDM_SPLIT_DUAL_WITH_{graph_part_tool.upper()}")) - - if iso_kind=="FIELD": - for i_part, part_zone in enumerate(part_zones): - pdm_isos.part_field_set(i_part, iso_params[i_part]) - else: - _KIND_TO_SET_FUNC[iso_kind](pdm_isos, *iso_params) - - # > Discover BCs and GCs over part_zones - dist_zone = PT.new_Zone('Zone') - # > BCs - dist_from_part.discover_nodes_from_matching(dist_zone, part_zones, ["ZoneBC_t", 'BC_t'], comm) - gdom_bcs_path = PT.predicates_to_paths(dist_zone, ['ZoneBC_t','BC_t']) - n_gdom_bcs = len(gdom_bcs_path) - # > GCs - is_gc = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - isnt_gc_intra= lambda n: is_gc(n) and PT.GridConnectivity.is1to1(n) and not MT.conv.is_intra_gc(PT.get_name(n)) - is_unmatched = lambda n: is_gc(n) and not PT.GridConnectivity.is1to1(n) - gc_predicate = ['ZoneGridConnectivity_t', is_unmatched] - dist_from_part.discover_nodes_from_matching(dist_zone, part_zones, gc_predicate, comm, get_value='leaf') - gc_predicate = ['ZoneGridConnectivity_t', isnt_gc_intra] - dist_from_part.discover_nodes_from_matching(dist_zone, part_zones, gc_predicate, comm, - merge_rule=lambda path: MT.conv.get_split_prefix(path), get_value='leaf') - for jn in PT.iter_children_from_predicates(dist_zone, gc_predicate): - val = PT.get_value(jn) - PT.set_value(jn, MT.conv.get_part_prefix(val)) - gdom_gcs_path = PT.predicates_to_paths(dist_zone, ['ZoneGridConnectivity_t',is_gc]) - n_gdom_gcs = len(gdom_gcs_path) - - # Loop over domain zones - for i_part, part_zone in enumerate(part_zones): - cx, cy, cz = PT.Zone.coordinates(part_zone) - vtx_coords = np_utils.interweave_arrays([cx,cy,cz]) - - ngon = PT.Zone.NGonNode(part_zone) - nface = PT.Zone.NFaceNode(part_zone) - - cell_face_idx = PT.get_child_from_name(nface, "ElementStartOffset" )[1] - cell_face = PT.get_child_from_name(nface, "ElementConnectivity")[1] - face_vtx_idx = PT.get_child_from_name(ngon, "ElementStartOffset" )[1] - face_vtx = PT.get_child_from_name(ngon, "ElementConnectivity")[1] - - vtx_ln_to_gn, _, face_ln_to_gn, cell_ln_to_gn = TEU.get_entities_numbering(part_zone) - - n_cell = cell_ln_to_gn.shape[0] - n_face = face_ln_to_gn.shape[0] - n_edge = 0 - n_vtx = vtx_ln_to_gn .shape[0] - - # Partition definition for PDM object - pdm_isos.part_set(i_part, - n_cell, n_face, n_edge, n_vtx, - cell_face_idx, cell_face, - None, None, None, - face_vtx_idx , face_vtx , - cell_ln_to_gn, face_ln_to_gn, - None, - vtx_ln_to_gn, vtx_coords) - - # Add BC information - if elt_type in ['TRI_3']: - all_bnd_pl = list() - for bnd_path in gdom_bcs_path: - bnd_n = PT.get_node_from_path(part_zone, bnd_path) - if bnd_n is not None: - all_bnd_pl.append(PT.get_value(PT.get_child_from_name(bnd_n, 'PointList'))) - else : - all_bnd_pl.append(np.empty((1,0), np.int32)) - for bnd_path in gdom_gcs_path: - # For gc, we glue the joins that have been splitted during partitioning - container_name, jn_name = bnd_path.split('/') - bnd_n_list = PT.get_nodes_from_names(part_zone, [container_name, jn_name+'*']) - if len(bnd_n_list) > 0: - all_bnd_pl.append(np_utils.concatenate_np_arrays([PT.get_node_from_name(bnd_n, 'PointList')[1] for bnd_n in bnd_n_list])[1]) - else: - all_bnd_pl.append(np.empty((1,0), np.int32)) - - group_face_idx, group_face = np_utils.concatenate_point_list(all_bnd_pl, dtype=np.int32) - pdm_isos.isosurf_bnd_set(i_part, n_gdom_bcs+n_gdom_gcs, group_face_idx, group_face) - - # Isosurfaces compute in PDM - pdm_isos.compute() - - # Mesh build from result - results = pdm_isos.part_iso_surface_surface_get() - n_iso_vtx = results['np_vtx_ln_to_gn'].shape[0] - n_iso_elt = results['np_elt_ln_to_gn'].shape[0] - - # > Zone construction (Zone.P{rank}.N0 because one part of zone on every proc a priori) - iso_part_zone = PT.new_Zone(PT.maia.conv.add_part_suffix('Zone', comm.Get_rank(), 0), - size=[[n_iso_vtx, n_iso_elt, 0]], - type='Unstructured') - - # > Grid coordinates - cx, cy, cz = layouts.interlaced_to_tuple_coords(results['np_vtx_coord']) - iso_grid_coord = PT.new_GridCoordinates(parent=iso_part_zone) - PT.new_DataArray('CoordinateX', cx, parent=iso_grid_coord) - PT.new_DataArray('CoordinateY', cy, parent=iso_grid_coord) - PT.new_DataArray('CoordinateZ', cz, parent=iso_grid_coord) - - # > Elements - if elt_type in ['TRI_3', 'QUAD_4']: - elt_n = PT.new_Elements(elt_type, - type=elt_type, - erange=[1, n_iso_elt], - econn=results['np_elt_vtx'], - parent=iso_part_zone) - PT.maia.newGlobalNumbering({'Element' : results['np_elt_ln_to_gn'], - 'Sections': results['np_elt_ln_to_gn']}, parent=elt_n) - else: - elt_n = PT.new_NGonElements('NGonElements', - erange = [1, n_iso_elt], - ec=results['np_elt_vtx'], - eso=results['np_elt_vtx_idx'], - parent=iso_part_zone) - PT.maia.newGlobalNumbering({'Element' : results['np_elt_ln_to_gn']}, parent=elt_n) - - # Bnd edges - if elt_type in ['TRI_3']: - # > Add element node - results_edge = pdm_isos.isosurf_bnd_get() - n_bnd_edge = results_edge['n_bnd_edge'] - bnd_edge_group_idx = results_edge['bnd_edge_group_idx'] - if n_bnd_edge!=0: - bar_n = PT.new_Elements('BAR_2', type='BAR_2', - erange=np.array([n_iso_elt+1, n_iso_elt+n_bnd_edge]), - econn=results_edge['bnd_edge_vtx'], - parent=iso_part_zone) - PT.maia.newGlobalNumbering({'Element' : results_edge['bnd_edge_lngn'], - 'Sections': results_edge['bnd_edge_lngn']}, parent=bar_n) - - # > Create BC described by edges - gnum = PT.maia.getGlobalNumbering(bar_n, 'Element')[1] if n_bnd_edge!=0 else np.empty(0, dtype=pdm_gnum_dtype) - for i_group, bc_path in enumerate(gdom_bcs_path): - n_edge_in_bc = bnd_edge_group_idx[i_group+1]-bnd_edge_group_idx[i_group] - edge_pl = np.arange(bnd_edge_group_idx[i_group ],\ - bnd_edge_group_idx[i_group+1], dtype=np.int32).reshape((1,-1), order='F')+n_iso_elt+1 - partial_gnum = create_sub_numbering([gnum[edge_pl[0]-n_iso_elt-1]], comm)[0] - - if partial_gnum.size != 0: - zonebc_n = PT.update_child(iso_part_zone, 'ZoneBC', 'ZoneBC_t') - bc_n = PT.new_BC(PT.path_tail(bc_path), point_list=edge_pl, loc="EdgeCenter", parent=zonebc_n) - PT.maia.newGlobalNumbering({'Index' : partial_gnum}, parent=bc_n) - - for i_group, gc_path in enumerate(gdom_gcs_path): - gc_name = PT.path_tail(gc_path) - gc_val = PT.get_value(PT.get_node_from_path(dist_zone, gc_path)) - - i_group+=n_gdom_bcs - - n_edge_in_gc = bnd_edge_group_idx[i_group+1]-bnd_edge_group_idx[i_group] - edge_pl = np.arange(bnd_edge_group_idx[i_group ],\ - bnd_edge_group_idx[i_group+1], dtype=np.int32).reshape((1,-1), order='F')+n_iso_elt+1 - partial_gnum = create_sub_numbering([gnum[edge_pl[0]-n_iso_elt-1]], comm)[0] - - if edge_pl.size != 0: - zonebc_n = PT.update_child(iso_part_zone, 'ZoneBC', 'ZoneBC_t') - bc_n = PT.new_BC(gc_name, point_list=edge_pl, loc="EdgeCenter", parent=zonebc_n) - PT.maia.newGlobalNumbering({'Index' : partial_gnum}, parent=bc_n) - - - else: - n_bnd_edge = 0 - - - # > LN to GN - PT.maia.newGlobalNumbering({'Vertex' : results['np_vtx_ln_to_gn'], - 'Cell' : results['np_elt_ln_to_gn'] }, parent=iso_part_zone) - - # > Link between vol and isosurf - maia_iso_zone = PT.new_node('maia#surface_data', label='UserDefinedData_t', parent=iso_part_zone) - results_vtx = pdm_isos.part_iso_surface_vtx_interpolation_data_get() - results_geo = pdm_isos.part_iso_surface_geom_data_get() - PT.new_DataArray('Cell_parent_gnum' , results ["np_elt_parent_g_num"] , parent=maia_iso_zone) - PT.new_DataArray('Vtx_parent_gnum' , results_vtx["vtx_volume_vtx_g_num"] , parent=maia_iso_zone) - PT.new_DataArray('Vtx_parent_idx' , results_vtx["vtx_volume_vtx_idx"] , parent=maia_iso_zone) - PT.new_DataArray('Vtx_parent_weight', results_vtx["vtx_volume_vtx_weight"], parent=maia_iso_zone) - PT.new_DataArray('Surface' , results_geo["elt_surface"] , parent=maia_iso_zone) - if elt_type in ['TRI_3'] and n_bnd_edge!=0: - PT.new_DataArray('Face_parent_bnd_edges', results_edge["bnd_edge_face_parent"], parent=maia_iso_zone) - - # > FamilyName(s) - dist_from_part.discover_nodes_from_matching(iso_part_zone, part_zones, [familyname_query], - comm, get_value='leaf') - - return iso_part_zone - - - -def _iso_surface(part_tree, iso_field_path, iso_val, elt_type, graph_part_tool, comm): - - fs_name, field_name = iso_field_path.split('/') - - # Get zones by domains - part_tree_per_dom = dist_from_part.get_parts_per_blocks(part_tree, comm) - - iso_part_tree = PT.new_CGNSTree() - - # Loop over domains : compute isosurf for each - for domain_path, part_zones in part_tree_per_dom.items(): - dom_base_name, dom_zone_name = domain_path.split('/') - iso_part_base = PT.update_child(iso_part_tree, dom_base_name, 'CGNSBase_t', [3-1,3]) - - field_values = [] - for part_zone in part_zones: - # Check : vertex centered solution (PDM_isosurf doesnt work with cellCentered field) - flowsol_node = PT.get_child_from_name(part_zone, fs_name) - field_node = PT.get_child_from_name(flowsol_node, field_name) - assert PT.Subset.GridLocation(flowsol_node) == "Vertex" - field_values.append(PT.get_value(field_node) - iso_val) - - iso_part_zone = iso_surface_one_domain(part_zones, "FIELD", field_values, elt_type, graph_part_tool, comm) - PT.set_name(iso_part_zone, PT.maia.conv.add_part_suffix(f'{dom_zone_name}', comm.Get_rank(), 0)) - if PT.Zone.n_cell(iso_part_zone)!=0: - PT.add_child(iso_part_base,iso_part_zone) - - copy_referenced_families(PT.get_all_CGNSBase_t(part_tree)[0], iso_part_base) - - return iso_part_tree - - -def iso_surface(part_tree, iso_field, comm, iso_val=0., containers_name=[], **options): - """ Create an isosurface from the provided field and value on the input partitioned tree. - - Isosurface is returned as an independant (2d) partitioned CGNSTree. - - Important: - - Input tree must be unstructured and have a ngon connectivity. - - Input tree must have been partitioned with ``preserve_orientation=True`` partitioning option. - - Input field for isosurface computation must be located at vertices. - - This function requires ParaDiGMa access. - - Note: - - Once created, additional fields can be exchanged from volumic tree to isosurface tree using - ``_exchange_field(part_tree, iso_part_tree, containers_name, comm)``. - - If ``elt_type`` is set to 'TRI_3', boundaries from volumic mesh are extracted as edges on - the isosurface (GridConnectivity_t nodes become BC_t nodes) and FaceCenter fields are allowed to be exchanged. - - Args: - part_tree (CGNSTree) : Partitioned tree on which isosurf is computed. Only U-NGon - connectivities are managed. - iso_field (str) : Path (starting at Zone_t level) of the field to use to compute isosurface. - comm (MPIComm) : MPI communicator - iso_val (float, optional) : Value to use to compute isosurface. Defaults to 0. - containers_name (list of str) : List of the names of the FlowSolution_t nodes to transfer - on the output isosurface tree. - **options: Options related to plane extraction. - Returns: - CGNSTree: Surfacic tree (partitioned) - - Isosurface can be controled thought the optional kwargs: - - - ``elt_type`` (str) -- Controls the shape of elements used to describe - the isosurface. Admissible values are ``TRI_3, QUAD_4, NGON_n``. Defaults to ``TRI_3``. - - ``graph_part_tool`` (str) -- Controls the isosurface partitioning tool. - Admissible values are ``hilbert, parmetis, ptscotch``. - ``hilbert`` may produce unbalanced partitions for some configurations. Defaults to ``ptscotch``. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #compute_iso_surface@start - :end-before: #compute_iso_surface@end - :dedent: 2 - """ - start = time.time() - - elt_type = options.get("elt_type", "TRI_3") - graph_part_tool = options.get("graph_part_tool", "ptscotch") - assert(elt_type in ["TRI_3","QUAD_4","NGON_n"]) - assert(graph_part_tool in ["ptscotch","parmetis","hilbert"]) - - # Isosurface extraction - iso_part_tree = _iso_surface(part_tree, iso_field, iso_val, elt_type, graph_part_tool, comm) - - # Interpolation - if containers_name: - _exchange_field(part_tree, iso_part_tree, containers_name, comm) - - end = time.time() - mlog.info(f"Isosurface completed ({end-start:.2f} s)") - - return iso_part_tree - - - -def _surface_from_equation(part_tree, surface_type, equation, elt_type, graph_part_tool, comm): - - assert(surface_type in ["PLANE","SPHERE","ELLIPSE"]) - assert(elt_type in ["TRI_3","QUAD_4","NGON_n"]) - - # Get zones by domains - part_tree_per_dom = dist_from_part.get_parts_per_blocks(part_tree, comm) - - iso_part_tree = PT.new_CGNSTree() - - # Loop over domains : compute isosurf for each - for domain_path, part_zones in part_tree_per_dom.items(): - dom_base_name, dom_zone_name = domain_path.split('/') - iso_part_base = PT.update_child(iso_part_tree, dom_base_name, 'CGNSBase_t', [3-1,3]) - iso_part_zone = iso_surface_one_domain(part_zones, surface_type, equation, elt_type, graph_part_tool, comm) - PT.set_name(iso_part_zone, PT.maia.conv.add_part_suffix(f'{dom_zone_name}', comm.Get_rank(), 0)) - - if PT.Zone.n_cell(iso_part_zone)!=0: - PT.add_child(iso_part_base,iso_part_zone) - - copy_referenced_families(PT.get_all_CGNSBase_t(part_tree)[0], iso_part_base) - - return iso_part_tree - - -def plane_slice(part_tree, plane_eq, comm, containers_name=[], **options): - """ Create a slice from the provided plane equation :math:`ax + by + cz - d = 0` - on the input partitioned tree. - - Slice is returned as an independant (2d) partitioned CGNSTree. See :func:`iso_surface` - for use restrictions and additional advices. - - Args: - part_tree (CGNSTree) : Partitioned tree to slice. Only U-NGon connectivities are managed. - sphere_eq (list of float): List of 4 floats :math:`[a,b,c,d]` defining the plane equation. - comm (MPIComm) : MPI communicator - containers_name (list of str) : List of the names of the FlowSolution_t nodes to transfer - on the output slice tree. - **options: Options related to plane extraction (see :func:`iso_surface`). - Returns: - CGNSTree: Surfacic tree (partitioned) - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #compute_plane_slice@start - :end-before: #compute_plane_slice@end - :dedent: 2 - """ - start = time.time() - - elt_type = options.get("elt_type", "TRI_3") - graph_part_tool = options.get("graph_part_tool", "ptscotch") - assert(elt_type in ["TRI_3","QUAD_4","NGON_n"]) - assert(graph_part_tool in ["ptscotch","parmetis","hilbert"]) - - # Isosurface extraction - iso_part_tree = _surface_from_equation(part_tree, 'PLANE', plane_eq, elt_type, graph_part_tool, comm) - - # Interpolation - if containers_name: - _exchange_field(part_tree, iso_part_tree, containers_name, comm) - - end = time.time() - mlog.info(f"Plane slice completed ({end-start:.2f} s)") - - return iso_part_tree - - -def spherical_slice(part_tree, sphere_eq, comm, containers_name=[], **options): - """ Create a spherical slice from the provided equation - :math:`(x-x_0)^2 + (y-y_0)^2 + (z-z_0)^2 = R^2` - on the input partitioned tree. - - Slice is returned as an independant (2d) partitioned CGNSTree. See :func:`iso_surface` - for use restrictions and additional advices. - - Args: - part_tree (CGNSTree) : Partitioned tree to slice. Only U-NGon connectivities are managed. - plane_eq (list of float): List of 4 floats :math:`[x_0, y_0, z_0, R]` defining the sphere equation. - comm (MPIComm) : MPI communicator - containers_name (list of str) : List of the names of the FlowSolution_t nodes to transfer - on the output slice tree. - **options: Options related to plane extraction (see :func:`iso_surface`). - Returns: - CGNSTree: Surfacic tree (partitioned) - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #compute_spherical_slice@start - :end-before: #compute_spherical_slice@end - :dedent: 2 - """ - start = time.time() - - elt_type = options.get("elt_type", "TRI_3") - graph_part_tool = options.get("graph_part_tool", "ptscotch") - assert(elt_type in ["TRI_3","QUAD_4","NGON_n"]) - assert(graph_part_tool in ["ptscotch","parmetis","hilbert"]) - - # Isosurface extraction - iso_part_tree = _surface_from_equation(part_tree, 'SPHERE', sphere_eq, elt_type, graph_part_tool, comm) - - # Interpolation - if containers_name: - _exchange_field(part_tree, iso_part_tree, containers_name, comm) - - end = time.time() - mlog.info(f"Spherical slice completed ({end-start:.2f} s)") - - return iso_part_tree - - -def elliptical_slice(part_tree, ellipse_eq, comm, containers_name=[], **options): - """ Create a elliptical slice from the provided equation - :math:`(x-x_0)^2/a^2 + (y-y_0)^2/b^2 + (z-z_0)^2/c^2 = R^2` - on the input partitioned tree. - - Slice is returned as an independant (2d) partitioned CGNSTree. See :func:`iso_surface` - for use restrictions and additional advices. - - Args: - part_tree (CGNSTree) : Partitioned tree to slice. Only U-NGon connectivities are managed. - ellispe_eq (list of float): List of 7 floats :math:`[x_0, y_0, z_0, a, b, c, R^2]` - defining the ellipse equation. - comm (MPIComm) : MPI communicator - containers_name (list of str) : List of the names of the FlowSolution_t nodes to transfer - on the output slice tree. - **options: Options related to plane extraction (see :func:`iso_surface`). - Returns: - CGNSTree: Surfacic tree (partitioned) - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #compute_elliptical_slice@start - :end-before: #compute_elliptical_slice@end - :dedent: 2 - """ - start = time.time() - - elt_type = options.get("elt_type", "TRI_3") - graph_part_tool = options.get("graph_part_tool", "ptscotch") - assert(elt_type in ["TRI_3","QUAD_4","NGON_n"]) - assert(graph_part_tool in ["ptscotch","parmetis","hilbert"]) - - # Isosurface extraction - iso_part_tree = _surface_from_equation(part_tree, 'ELLIPSE', ellipse_eq, elt_type, graph_part_tool, comm) - - # Interpolation - if containers_name: - _exchange_field(part_tree, iso_part_tree, containers_name, comm) - - end = time.time() - mlog.info(f"Elliptical slice completed ({end-start:.2f} s)") - - return iso_part_tree diff --git a/maia/algo/part/localize.py b/maia/algo/part/localize.py deleted file mode 100644 index 0b04f58c..00000000 --- a/maia/algo/part/localize.py +++ /dev/null @@ -1,188 +0,0 @@ -import numpy as np - -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.utils import py_utils, np_utils, par_utils -from maia.transfer import utils as te_utils -from maia.factory.dist_from_part import get_parts_per_blocks - -from .point_cloud_utils import get_shifted_point_clouds - -def _get_part_data(part_zone): - cx, cy, cz = PT.Zone.coordinates(part_zone) - vtx_coords = np_utils.interweave_arrays([cx,cy,cz]) - - ngon = PT.Zone.NGonNode(part_zone) - nface = PT.Zone.NFaceNode(part_zone) - - cell_face_idx = PT.get_child_from_name(nface, "ElementStartOffset")[1] - cell_face = PT.get_child_from_name(nface, "ElementConnectivity")[1] - face_vtx_idx = PT.get_child_from_name(ngon, "ElementStartOffset")[1] - face_vtx = PT.get_child_from_name(ngon, "ElementConnectivity")[1] - - vtx_ln_to_gn, _, face_ln_to_gn, cell_ln_to_gn = te_utils.get_entities_numbering(part_zone) - - return [cell_face_idx, cell_face, cell_ln_to_gn, \ - face_vtx_idx, face_vtx, face_ln_to_gn, vtx_coords, vtx_ln_to_gn] - -def _mesh_location(src_parts, tgt_clouds, comm, reverse=False, loc_tolerance=1E-6): - """ Wrapper of PDM mesh location - For now, only 1 domain is supported so we expect source parts and target clouds - as flat lists : - Parts are tuple (cell_face_idx, cell_face, cell_lngn, - face_vtx_idx, face_vtx, face_lngn, vtx_coords, vtx_lngn) - Cloud are tuple (coords, lngn) - """ - - n_part_src = len(src_parts) - n_part_tgt = len(tgt_clouds) - # > Create and setup global data - mesh_loc = PDM.MeshLocation(n_point_cloud=1, comm=comm) - mesh_loc.mesh_n_part_set(n_part_src) # For now only one domain is supported - mesh_loc.n_part_cloud_set(0, n_part_tgt) # For now only one domain is supported - - # > Register source - for i_part, part_data in enumerate(src_parts): - cell_face_idx, cell_face, cell_ln_to_gn, face_vtx_idx, face_vtx, face_ln_to_gn, \ - vtx_coords, vtx_ln_to_gn = part_data - mesh_loc.part_set(i_part, cell_face_idx, cell_face, cell_ln_to_gn, - face_vtx_idx, face_vtx, face_ln_to_gn, - vtx_coords, vtx_ln_to_gn) - # > Setup target - for i_part, (coords, lngn) in enumerate(tgt_clouds): - mesh_loc.cloud_set(0, i_part, coords, lngn) - - mesh_loc.tolerance = loc_tolerance - mesh_loc.compute() - - # This is located and unlocated indices - all_located_id = [mesh_loc.located_get (0,i_part) for i_part in range(n_part_tgt)] - all_unlocated_id = [mesh_loc.unlocated_get(0,i_part) for i_part in range(n_part_tgt)] - - #This is result from the target perspective (api : (i_pt_cloud, i_part)) - all_target_data = [mesh_loc.location_get(0, i_tgt_part) for i_tgt_part in range(n_part_tgt)] - # Add ids in dict - for i_part, data in enumerate(all_target_data): - data.pop('g_num') - data['located_ids'] = all_located_id[i_part] - 1 - data['unlocated_ids'] = all_unlocated_id[i_part] - 1 - - #This is result from the source perspective (api : ((i_part, i_pt_cloud)) - if reverse: - all_located_inv = [mesh_loc.points_in_elt_get(0, i_src_part) for i_src_part in range(n_part_src)] - return all_target_data, all_located_inv - else: - return all_target_data - -def _localize_points(src_parts_per_dom, tgt_parts_per_dom, location, comm, \ - reverse=False, loc_tolerance=1E-6): - """ - """ - locs = ['Cell', 'Face', 'Vtx'] - n_dom_src = len(src_parts_per_dom) - n_dom_tgt = len(tgt_parts_per_dom) - - n_part_per_dom_src = [len(parts) for parts in src_parts_per_dom] - n_part_per_dom_tgt = [len(parts) for parts in tgt_parts_per_dom] - n_part_src = sum(n_part_per_dom_src) - n_part_tgt = sum(n_part_per_dom_tgt) - - # > Register source - src_parts = [] - src_offsets = {loc : np.zeros(n_dom_src+1, dtype=pdm_gnum_dtype) for loc in locs} - for i_domain, src_part_zones in enumerate(src_parts_per_dom): - src_parts_domain = [_get_part_data(src_part) for src_part in src_part_zones] - # Compute global offsets for this domain - for array_idx, loc in zip([2,5,7], locs): - dom_max = par_utils.arrays_max([src_part[array_idx] for src_part in src_parts_domain], comm) - src_offsets[loc][i_domain+1] = src_offsets[loc][i_domain] + dom_max - # Shift source arrays (inplace) - for src_part in src_parts_domain: - src_part[2] += src_offsets['Cell'][i_domain] #cell_ln_to_gn - src_part[5] += src_offsets['Face'][i_domain] #face_ln_to_gn - src_part[7] += src_offsets['Vtx' ][i_domain] #vtx_ln_to_gn - src_parts.extend(src_parts_domain) - - tgt_offset, tgt_clouds = get_shifted_point_clouds(tgt_parts_per_dom, location, comm) - tgt_clouds = py_utils.to_flat_list(tgt_clouds) - - result = _mesh_location(src_parts, tgt_clouds, comm, reverse, loc_tolerance) - - # Shift back source data - for i_domain, src_parts_domain in enumerate(py_utils.to_nested_list(src_parts, n_part_per_dom_src)): - for src_part in src_parts_domain: - src_part[2] -= src_offsets['Cell'][i_domain] #cell_ln_to_gn - src_part[5] -= src_offsets['Face'][i_domain] #face_ln_to_gn - src_part[7] -= src_offsets['Vtx' ][i_domain] #vtx_ln_to_gn - - # Shift results and get domain ids - direct_result = result[0] if reverse else result - for tgt_result in direct_result: - tgt_result['location_shifted'] = tgt_result.pop('location') #Rename key - tgt_result['location'], tgt_result['domain'] = np_utils.shifted_to_local( - tgt_result['location_shifted'], src_offsets['Cell']) - if reverse: - for src_result in result[1]: - src_result['points_gnum_shifted'] = src_result.pop('points_gnum') #Rename key - src_result['points_gnum'], src_result['domain'] = np_utils.shifted_to_local( - src_result['points_gnum_shifted'], tgt_offset) - - # Reshape output to list of lists (as input domains) - if reverse: - return py_utils.to_nested_list(result[0], n_part_per_dom_tgt),\ - py_utils.to_nested_list(result[1], n_part_per_dom_src) - else: - return py_utils.to_nested_list(result, n_part_per_dom_tgt) - -def localize_points(src_tree, tgt_tree, location, comm, **options): - """Localize points between two partitioned trees. - - For all the points of the target tree matching the given location, - search the cell of the source tree in which it is enclosed. - The result, i.e. the gnum & domain number of the source cell (or -1 if the point is not localized), - are stored in a ``DiscreteData_t`` container called "Localization" on the target zones. - - Source tree must be unstructured and have a NGon connectivity. - - Localization can be parametred thought the options kwargs: - - - ``loc_tolerance`` (default = 1E-6) -- Geometric tolerance for the method. - - Args: - src_tree (CGNSTree): Source tree, partitionned. Only U-NGon connectivities are managed. - tgt_tree (CGNSTree): Target tree, partitionned. Structured or U-NGon connectivities are managed. - location ({'CellCenter', 'Vertex'}) : Target points to localize - comm (MPIComm): MPI communicator - **options: Additional options related to location strategy - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #localize_points@start - :end-before: #localize_points@end - :dedent: 2 - """ - _src_parts_per_dom = get_parts_per_blocks(src_tree, comm) - src_parts_per_dom = list(_src_parts_per_dom.values()) - tgt_parts_per_dom = list(get_parts_per_blocks(tgt_tree, comm).values()) - - located_data = _localize_points(src_parts_per_dom, tgt_parts_per_dom, location, comm, **options) - - dom_list = '\n'.join(_src_parts_per_dom.keys()) - for i_dom, tgt_parts in enumerate(tgt_parts_per_dom): - for i_part, tgt_part in enumerate(tgt_parts): - sol = PT.update_child(tgt_part, "Localization", "DiscreteData_t") - PT.new_GridLocation(location, sol) - data = located_data[i_dom][i_part] - n_tgts = data['located_ids'].size + data['unlocated_ids'].size, - src_gnum = -np.ones(n_tgts, dtype=pdm_gnum_dtype) #Init with -1 to carry unlocated points - src_dom = -np.ones(n_tgts, dtype=np.int32) - src_gnum[data['located_ids']] = data['location'] - src_dom [data['located_ids']] = data['domain'] - PT.new_DataArray("SrcId", src_gnum, parent=sol) - PT.new_DataArray("DomId", src_dom, parent=sol) - PT.new_node("DomainList", "Descriptor_t", dom_list, parent=sol) - diff --git a/maia/algo/part/move_loc.py b/maia/algo/part/move_loc.py deleted file mode 100644 index 9b9c728a..00000000 --- a/maia/algo/part/move_loc.py +++ /dev/null @@ -1,237 +0,0 @@ -import numpy as np - -import maia -import maia.pytree as PT - -from maia.factory.dist_from_part import get_parts_per_blocks - -from . import multidom_gnum -from . import connectivity_utils -from . import geometry - -import Pypdm.Pypdm as PDM - -class CenterToNode: - - def __init__(self, tree, comm, idw_power=1, cross_domain=True): - - self.parts = [] - self.weights = [] - self.vtx_cell = [] - self.vtx_cell_idx = [] - - parts_per_dom = get_parts_per_blocks(tree, comm) - vtx_gnum_shifted = multidom_gnum.get_mdom_gnum_vtx(parts_per_dom, comm, cross_domain) - - gnum_list = [] - for i_dom, zone_path in enumerate(parts_per_dom): - dim = PT.get_value(PT.get_child_from_name(tree, PT.path_head(zone_path)))[0] - for i_part, zone in enumerate(parts_per_dom[zone_path]): - - n_vtx = PT.Zone.n_vtx(zone) - cell_vtx_idx, cell_vtx = connectivity_utils.cell_vtx_connectivity(zone, dim) - vtx_cell_idx, vtx_cell = PDM.connectivity_transpose(int(n_vtx), cell_vtx_idx, cell_vtx) - - # Compute the distance between vertices and cellcenters - cx,cy,cz = PT.Zone.coordinates(zone) - if PT.Zone.Type(zone)=='Structured' : - cx = cx.flatten() - cy = cy.flatten() - cz = cz.flatten() - # Use direct api since cell_vtx is already computed - cell_center = geometry.centers._mean_coords_from_connectivity(cell_vtx_idx, cell_vtx, cx,cy,cz) - - # This one is just the local index of each vertices, repeated for - # each cell the vertex touches. Eg [0, 1, 1, 2,2,2,2] if vtx 0, - # 1 and 2 belongs to 1, 2 and 4 cells. It it used to - # compute vtx -> cell center distance for each connected cell - vtx_idx_rep = np.repeat(np.arange(n_vtx), np.diff(vtx_cell_idx)) - - diff_x = cx[vtx_idx_rep] - cell_center[0::3][vtx_cell-1] - diff_y = cy[vtx_idx_rep] - cell_center[1::3][vtx_cell-1] - diff_z = cz[vtx_idx_rep] - cell_center[2::3][vtx_cell-1] - norm_rep = (diff_x**2 + diff_y**2 + diff_z**2)**(0.5*idw_power) - - gnum_rep = vtx_gnum_shifted[i_dom][i_part][vtx_idx_rep] - - gnum_list.append(gnum_rep) - - # Store objects needed for exchange - self.parts.append(zone) - self.weights.append(1./norm_rep) - self.vtx_cell.append(vtx_cell) - self.vtx_cell_idx.append(vtx_cell_idx) # This one will be usefull to go back to unique vtx value - - self.gmean = PDM.GlobalMean(gnum_list, comm) - - - def move_fields(self, container_name): - - #Check that solutions are known on each source partition - fields_per_part = list() - for part in self.parts: - container = PT.get_node_from_path(part, container_name) - assert PT.Subset.GridLocation(container) == 'CellCenter' - fields_name = sorted([PT.get_name(array) for array in PT.iter_children_from_label(container, 'DataArray_t')]) - fields_per_part.append(fields_name) - assert fields_per_part.count(fields_per_part[0]) == len(fields_per_part) - - #Collect src sol - cell_fields = {} - asflat = lambda val, zone : val.flatten(order='F') if PT.Zone.Type(zone) == 'Structured' else val - for field_name in fields_per_part[0]: - field_path = container_name + '/' + field_name - cell_fields[field_name] = [asflat(PT.get_node_from_path(part, field_path)[1], part)[vtx_cell-1].astype(float, copy=False) \ - for part, vtx_cell in zip(self.parts, self.vtx_cell)] - - # Do all reductions - node_fields = {} - for field_name, field_values in cell_fields.items(): - node_fields[field_name] = self.gmean.compute_field(field_values, self.weights) - - # Add node fields in tree - for i_part, part in enumerate(self.parts): - vtx_shape = PT.Zone.VertexSize(part) - is_struct = PT.Zone.Type(part) == 'Structured' - PT.rm_children_from_name(part, f'{container_name}#Vtx') - fs = PT.new_FlowSolution(f'{container_name}#Vtx', loc='Vertex', parent=part) - vtx_cell_idx = self.vtx_cell_idx[i_part] - for field_name, field_values in node_fields.items(): - data_out = field_values[i_part][vtx_cell_idx[:-1]] - if is_struct: - data_out = data_out.reshape(vtx_shape, order='F') - PT.new_DataArray(field_name, data_out, parent=fs) - -class NodeToCenter: - def __init__(self, tree, comm, idw_power=1): - - self.parts = [] - self.weights = [] - self.weightssum = [] - self.cell_vtx = [] - self.cell_vtx_idx = [] - - for base in PT.get_all_CGNSBase_t(tree): - dim = PT.get_value(base)[0] - for p_zone in PT.get_all_Zone_t(base): - cx,cy,cz = PT.Zone.coordinates(p_zone) - if PT.Zone.Type(p_zone)=='Structured' : - cx = cx.flatten() - cy = cy.flatten() - cz = cz.flatten() - cell_vtx_idx, cell_vtx = connectivity_utils.cell_vtx_connectivity(p_zone, dim) - cell_vtx_n = np.diff(cell_vtx_idx) - - # Use direct api since cell_vtx is already computed - cell_center = geometry.centers._mean_coords_from_connectivity(cell_vtx_idx, cell_vtx, cx,cy,cz) - - diff_x = cx[cell_vtx-1] - np.repeat(cell_center[0::3], cell_vtx_n) - diff_y = cy[cell_vtx-1] - np.repeat(cell_center[1::3], cell_vtx_n) - diff_z = cz[cell_vtx-1] - np.repeat(cell_center[2::3], cell_vtx_n) - norm = (diff_x**2 + diff_y**2 + diff_z**2)**(0.5*idw_power) - weights = 1./norm - - self.parts.append(p_zone) - self.weights.append(weights) - self.weightssum.append(np.add.reduceat(weights, cell_vtx_idx[:-1])) - self.cell_vtx.append(cell_vtx) - self.cell_vtx_idx.append(cell_vtx_idx) - - - def move_fields(self, container_name): - - for i_part, part in enumerate(self.parts): - cell_vtx_idx = self.cell_vtx_idx[i_part] - cell_vtx = self.cell_vtx [i_part] - weights = self.weights [i_part] - weightssum = self.weightssum[i_part] - - container = PT.get_node_from_path(part, container_name) - assert PT.Subset.GridLocation(container) == 'Vertex' - - PT.rm_children_from_name(part, f'{container_name}#Cell') - fs_out = PT.new_FlowSolution(f'{container_name}#Cell', loc='CellCenter', parent=part) - - for array in PT.iter_children_from_label(container, 'DataArray_t'): - data_in = PT.get_value(array) - shape = data_in.shape - if len(shape) != 1 : - data_in=data_in.flatten(order='F') - data_out = np.add.reduceat(data_in[cell_vtx-1] * weights, cell_vtx_idx[:-1]) - data_out /= weightssum - if len(shape) != 1 : - data_out=data_out.reshape(np.array(shape)-1, order='F') - PT.new_DataArray(PT.get_name(array), data_out, parent=fs_out) - - - - -def centers_to_nodes(tree, comm, containers_name=[], **options): - """ Create Vertex located FlowSolution_t from CellCenter located FlowSolution_t. - - Interpolation is based on Inverse Distance Weighting - `(IDW) `_ method: - each cell contributes to each of its vertices with a weight computed from the distance - between the cell isobarycenter and the vertice. The method can be tuned with - the following kwargs: - - - ``idw_power`` (float, default = 1) -- Power to which the cell-vertex distance is elevated. - - - ``cross_domain`` (bool, default = True) -- If True, vertices located at domain - interfaces also receive data from the opposite domain cells. This parameter does not - apply to internal partitioning interfaces, which are always crossed. - - Args: - tree (CGNSTree): Partionned tree - comm (MPIComm): MPI communicator - containers_name (list of str) : List of the names of the FlowSolution_t nodes to transfer. - **options: Options related to interpolation, see above. - - See also: - A :class:`CenterToNode` object can be instanciated with the same parameters, excluding ``containers_name``, - and then be used to move containers more than once with its - ``move_fields(container_name)`` method. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #centers_to_nodes@start - :end-before: #centers_to_nodes@end - :dedent: 2 - """ - C2N = CenterToNode(tree, comm, **options) - - for container_name in containers_name: - C2N.move_fields(container_name) - -def nodes_to_centers(tree, comm, containers_name=[], **options): - """ Create CellCenter located FlowSolution_t from Vertex located FlowSolution_t. - - Interpolation is based on Inverse Distance Weighting - `(IDW) `_ method: - each vertex contributes to the cell value with a weight computed from the distance - between the cell isobarycenter and the vertice. The method can be tuned with - the following kwargs: - - - ``idw_power`` (float, default = 1) -- Power to which the cell-vertex distance is elevated. - - Args: - tree (CGNSTree): Partionned tree - comm (MPIComm): MPI communicator - containers_name (list of str) : List of the names of the FlowSolution_t nodes to transfer. - **options: Options related to interpolation, see above. - - See also: - A :class:`NodeToCenter` object can be instanciated with the same parameters, excluding ``containers_name``, - and then be used to move containers more than once with its - ``move_fields(container_name)`` method. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #nodes_to_centers@start - :end-before: #nodes_to_centers@end - :dedent: 2 - """ - N2C = NodeToCenter(tree, comm, **options) - - for container_name in containers_name: - N2C.move_fields(container_name) diff --git a/maia/algo/part/multidom_gnum.py b/maia/algo/part/multidom_gnum.py deleted file mode 100644 index 30be6927..00000000 --- a/maia/algo/part/multidom_gnum.py +++ /dev/null @@ -1,146 +0,0 @@ -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import Pypdm.Pypdm as PDM - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.utils import np_utils, py_utils, par_utils, as_pdm_gnum -from maia.algo.dist import matching_jns_tools as MJT -from maia.factory import dist_from_part as DFP - -from maia.algo import dist as MAD - -from maia.transfer import protocols as EP -from maia.transfer.part_to_dist import data_exchange as PTB -from maia.transfer.dist_to_part import index_exchange as IBTP - -def _get_shifted_arrays(arrays_per_dom, comm): - shifted_per_dom = [] - offset = np.zeros(len(arrays_per_dom)+1, dtype=pdm_gnum_dtype) - for i_dom, arrays in enumerate(arrays_per_dom): - offset[i_dom+1] = offset[i_dom] + par_utils.arrays_max(arrays, comm) - shifted_per_dom.append([array + offset[i_dom] for array in arrays]) # Shift (with copy) - return offset, shifted_per_dom - -def get_shifted_ln_to_gn_from_loc(parts_per_dom, location, comm): - """ Wraps _get_zone_ln_to_gn_from_loc around multiple domains, - shifting lngn with previous values""" - from .point_cloud_utils import _get_zone_ln_to_gn_from_loc - lngns_per_dom = [] - for part_zones in parts_per_dom: - lngns_per_dom.append([_get_zone_ln_to_gn_from_loc(part, location) for part in part_zones]) - return _get_shifted_arrays(lngns_per_dom, comm) - -def get_mdom_gnum_vtx(parts_per_dom, comm, merge_jns=True): - - # Get gnum shifted for vertices - vtx_mdom_offsets, shifted_lngn = get_shifted_ln_to_gn_from_loc(parts_per_dom.values(), 'Vertex', comm) - - if not merge_jns: - return shifted_lngn - - # Now we want to give a common gnum to the vertices connected thought GC. The outline is: - # 1. go back to distribute vision of GC to build graph of connected vertices - # 2. Give to each group of connected vertices a gnum - # 3. Send this data to the partitionned GCs using a PartToPart. - # 4. Use the recv data to update the shifted_lngn - - - # 1. Go back to distribute vision of GC to build graph of connected vertices - dist_tree_jn = DFP._get_joins_dist_tree(parts_per_dom, comm) - - # If there is some FaceCenter interfaces, convert it to Vertex - tree_has_face_gc = False - face_loc_query = lambda n : PT.get_label(n) == 'GridLocation_t' and PT.get_value(n) == 'FaceCenter' - for dom_name, parts in parts_per_dom.items(): - dist_zone = PT.get_node_from_path(dist_tree_jn, dom_name) - has_face_gc = PT.get_node_from_predicate(dist_zone, face_loc_query) is not None - if has_face_gc: - # Vtx gnum is needed for face->vtx conversion. - # Connectivities should have been already added by _get_joins_dist_tree - vtx_lngn_l = [MT.getGlobalNumbering(part, 'Vertex')[1] for part in parts] - vtx_distri = PTB._lngn_to_distri(vtx_lngn_l, comm) - - MT.newDistribution({'Vertex' : vtx_distri}, parent=dist_zone) - tree_has_face_gc = True - - if tree_has_face_gc: - MJT.copy_donor_subset(dist_tree_jn) #Needed for Vertexlist - MAD.generate_jns_vertex_list(dist_tree_jn, comm, True) - - # We need to put vtx joins on partitioned trees, but recovering donor - # and order is useless - for dom_name, parts in parts_per_dom.items(): - dist_zone = PT.get_node_from_path(dist_tree_jn, dom_name) - IBTP.dist_pl_to_part_pl(dist_zone, parts, ['ZoneGridConnectivity_t/GridConnectivity_t'], 'Vertex', comm) - - - zone_to_dom = {dom : i for i, dom in enumerate(parts_per_dom.keys())} - - interface_ids_v = [] - interface_dom = [] - interface_dn_v = [] - - is_gc = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - is_vtx_gc = lambda n: is_gc(n) and PT.Subset.GridLocation(n) == 'Vertex' - is_vtx_gc_intra = lambda n: is_vtx_gc(n) and not MT.conv.is_intra_gc(PT.get_name(n)) - - for gc_path_cur in PT.predicates_to_paths(dist_tree_jn, ['CGNSBase_t', 'Zone_t', 'ZoneGridConnectivity_t', is_vtx_gc]): - gc_path_opp = MJT.get_jn_donor_path(dist_tree_jn, gc_path_cur) - - if gc_path_cur < gc_path_opp: - - pl = as_pdm_gnum(PT.get_node_from_path(dist_tree_jn, gc_path_cur+'/PointList')[1][0]) - pld = as_pdm_gnum(PT.get_node_from_path(dist_tree_jn, gc_path_opp+"/PointList")[1][0]) - - interface_dn_v.append(pl.size) - interface_ids_v.append(np_utils.interweave_arrays([pl,pld])) - interface_dom.append((zone_to_dom[PT.path_head(gc_path_cur,2)], - zone_to_dom[PT.path_head(gc_path_opp,2)])) - - if len(interface_dn_v) == 0: # Early return if no joins - return shifted_lngn - - graph_idx, graph_ids, graph_dom = PDM.interface_to_graph( len(interface_dn_v), False, - interface_dn_v, interface_ids_v, interface_dom, comm) - - - # 2. Give to each group of connected vertices a gnum (in 1 ... Nb of groups of connected vtx) - rank_offset = par_utils.gather_and_shift(graph_idx.size-1, comm)[comm.Get_rank()] - vtx_group_id = np.repeat(np.arange(1, graph_idx.size), np.diff(graph_idx)) + rank_offset - - # 3. Send this data to the partitionned GCs using a PartToPart. - - vtx_ggnum_graph = [graph_ids + vtx_mdom_offsets[graph_dom]] #Domain gnum on graph side - - vtx_ggnum_parts = [] - for vtx_mdom_offset, parts in zip(vtx_mdom_offsets, parts_per_dom.values()): - for part in parts: - vtx_gnum = as_pdm_gnum(MT.getGlobalNumbering(part, 'Vertex')[1]) - for gc in PT.get_children_from_predicates(part, ['ZoneGridConnectivity_t', is_vtx_gc_intra]): - pl = PT.get_child_from_name(gc, 'PointList')[1][0] - vtx_ggnum_parts.append(vtx_gnum[pl-1] + vtx_mdom_offset) #Domain gnum on part side - - # Create PTP : indirection part1topart2 is just the identity - vtx_group_id_recv = EP.part_to_part([vtx_group_id], vtx_ggnum_graph, vtx_ggnum_parts, comm) - - # 4. Use the recv data to update the shifted_lngn - - count = 0 - for shifted_lngn_dom, parts in zip(shifted_lngn, parts_per_dom.values()): - for shifted_lngn_part, part in zip(shifted_lngn_dom, parts): - for gc in PT.get_children_from_predicates(part, ['ZoneGridConnectivity_t', is_vtx_gc_intra]): - pl = PT.get_child_from_name(gc, 'PointList')[1][0] - # We received id starting at 1 so shift it to the end of the internal gc gnums - shifted_lngn_part[pl-1] = vtx_group_id_recv[count] + vtx_mdom_offsets[-1] - count += 1 - - # Finally we just have to fill holes - from .point_cloud_utils import create_sub_numbering - shifted_lngn_contiguous = create_sub_numbering(py_utils.to_flat_list(shifted_lngn), comm) - shifted_lngn_contiguous = py_utils.to_nested_list(shifted_lngn_contiguous, - [len(parts) for parts in parts_per_dom.values()]) - return shifted_lngn_contiguous - diff --git a/maia/algo/part/ngon_tools/__init__.py b/maia/algo/part/ngon_tools/__init__.py deleted file mode 100644 index 88d155a7..00000000 --- a/maia/algo/part/ngon_tools/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._ngon_tools import pe_to_nface, nface_to_pe diff --git a/maia/algo/part/ngon_tools/_ngon_tools.py b/maia/algo/part/ngon_tools/_ngon_tools.py deleted file mode 100644 index 4a7bb925..00000000 --- a/maia/algo/part/ngon_tools/_ngon_tools.py +++ /dev/null @@ -1,79 +0,0 @@ -import numpy as np - -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.algo import indexing -from maia.utils import np_utils - -import cmaia.part_algo as cpart_algo - -def pe_to_nface(zone, remove_PE=False): - """Create a NFace node from a NGon node with ParentElements. - - Input tree is modified inplace. - - Args: - zone (CGNSTree): Partitioned zone - remove_PE (bool, optional): If True, remove the ParentElements node. - Defaults to False. - """ - ngon_node = PT.Zone.NGonNode(zone) - er = PT.get_child_from_name(ngon_node, 'ElementRange')[1] - pe = PT.get_child_from_name(ngon_node, 'ParentElements')[1] - max_cell = np.max(pe) - min_cell = np.min(pe[np.nonzero(pe)]) - - local_pe = indexing.get_ngon_pe_local(ngon_node) - - nface_eso, nface_ec = cpart_algo.local_pe_to_local_cellface(local_pe) #Compute NFace connectivity - - #Put NFace/EC in global numbering (to refer to ngon global ids) - first_ngon = er[0] - if first_ngon != 1: - nface_ec_sign = np.sign(nface_ec) - nface_ec_sign*(np.abs(nface_ec) + first_ngon - 1) - - #Create NFace node - _erange = np.array([min_cell, max_cell], dtype=np.int32) - nface = PT.new_NFaceElements(erange=_erange, eso=nface_eso, ec=nface_ec, parent=zone) - cell_gnum = MT.getGlobalNumbering(zone, 'Cell') - if cell_gnum is not None: - MT.newGlobalNumbering({'Element' : PT.get_value(cell_gnum)}, nface) - - if remove_PE: - PT.rm_children_from_name(ngon_node, "ParentElements") - - -def nface_to_pe(zone, remove_NFace=False): - """Create a ParentElements node in the NGon node from a NFace node. - - Input tree is modified inplace. - - Args: - zone (CGNSTree): Partitioned zone - remove_NFace (bool, optional): If True, remove the NFace node. - Defaults to False. - """ - ngon_node = PT.Zone.NGonNode(zone) - nface_node = PT.Zone.NFaceNode(zone) - - cell_face_idx = PT.get_child_from_name(nface_node, "ElementStartOffset")[1] - cell_face = PT.get_child_from_name(nface_node, "ElementConnectivity")[1] - - # If NFace are before NGon, then face ids must be shifted - if PT.Element.Range(ngon_node)[0] == 1: - _cell_face = cell_face - else: - _cell_face_sign = np.sign(cell_face) - _cell_face = np.abs(cell_face) - PT.Element.Size(nface_node) - _cell_face = _cell_face * _cell_face_sign - - local_pe = cpart_algo.local_cellface_to_local_pe(cell_face_idx, _cell_face) - np_utils.shift_nonzeros(local_pe, PT.Element.Range(nface_node)[0]-1) # Refer to NFace global ids - - PT.new_DataArray('ParentElements', local_pe, parent=ngon_node) - if remove_NFace: - PT.rm_child(zone, nface_node) diff --git a/maia/algo/part/ngon_tools/ngon_tools.pybind.hpp b/maia/algo/part/ngon_tools/ngon_tools.pybind.hpp deleted file mode 100644 index 8bf0580a..00000000 --- a/maia/algo/part/ngon_tools/ngon_tools.pybind.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#include -#include -#include -#include - -#include "maia/utils/pybind_utils.hpp" - -namespace py = pybind11; - -// -------------------------------------------------------------------- -template -inline -std::tuple, py::array_t> -local_pe_to_local_cellface(py::array_t& np_pe) -{ - int n_face = np_pe.shape()[0]; - auto pe = make_raw_view(np_pe); - - // Find number of cells == max of PE - int n_cell = std::accumulate(pe, pe+2*n_face, T(0), [](auto x, auto y){ return std::max(x,y); }); - - // Count the number of occurences of each cell - std::vector counts(n_cell+1, 0); - for (int iface=0; iface < 2*n_face; ++iface) { - counts[pe[iface]]++; - } - - //Compute eso - std::vector eso(n_cell+1, 0); - eso[0] = 0; - std::partial_sum(counts.begin()+1, counts.end(), eso.begin()+1); - - //Allocate ec - py::array_t np_ec(eso[n_cell]); - auto ec = make_raw_view(np_ec); - - - // Now fill - std::fill(counts.begin(), counts.end(), 0); - for (int iface=0; iface < n_face; ++iface) { - int icell = pe[iface]; - if (icell > 0) { - ec[eso[icell-1]+counts[icell]++] = iface + 1; - } - } - for (int iface=0; iface < n_face; ++iface) { - int icell = pe[n_face + iface]; - if (icell > 0) { - ec[eso[icell-1]+counts[icell]++] = -(iface + 1); - } - } - return std::make_tuple(py::cast(eso), np_ec); -} - -// -------------------------------------------------------------------- -inline -py::array_t -local_cellface_to_local_pe(py::array_t& np_cellface_idx, - py::array_t& np_cellface) -{ - int n_cell = np_cellface_idx.size() - 1; - auto cellface_idx = np_cellface_idx.unchecked<1>(); - auto cellface = np_cellface.unchecked<1>(); - - // Find number of faces == max of EC - int n_face = 0; - for (int i=0; i < np_cellface.size(); ++i) { - n_face = std::max(n_face, std::abs(cellface[i])); - } - - // Declare and init PE - py::array_t np_pe({n_face,2}); - auto pe = make_raw_view(np_pe); - for (int i=0; i < 2*n_face; i++) { - pe[i] = 0; - } - - //Fill PE - for (int icell=0; icell < n_cell; ++icell) { - for (int j=cellface_idx[icell]; j < cellface_idx[icell+1]; j++) { - int iface = cellface[j]; - if (iface > 0) { - pe[std::abs(iface)-1] = icell+1; - } - else { - pe[std::abs(iface) -1 + n_face] = icell+1; - } - } - } - - return np_pe; -} diff --git a/maia/algo/part/ngon_tools/test/test_pngon_tools.py b/maia/algo/part/ngon_tools/test/test_pngon_tools.py deleted file mode 100644 index 750f7e60..00000000 --- a/maia/algo/part/ngon_tools/test/test_pngon_tools.py +++ /dev/null @@ -1,56 +0,0 @@ -import pytest -import pytest_parallel - -import numpy as np - -import maia.pytree as PT - -from maia.factory import dcube_generator as DCG -from maia.algo.part import ngon_tools as NGT - -def as_partitioned(zone): - PT.rm_nodes_from_name(zone, ":CGNS#Distribution") - predicate = lambda n : PT.get_label(n) in ['Zone_t', 'DataArray_t', 'IndexArray_t', 'IndexRange_t'] \ - and PT.get_value(n).dtype == np.int64 - for array in PT.iter_nodes_from_predicate(zone, predicate, explore='deep'): - array[1] = array[1].astype(np.int32) - -@pytest_parallel.mark.parallel([1]) -def test_pe_to_nface(comm): - tree = DCG.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - as_partitioned(zone) - - nface_er_exp = np.array([37,44], np.int32) - nface_eso_exp = np.array([0, 6, 12, 18, 24, 30, 36, 42, 48], np.int32) - nface_ec_exp = np.array([1,5,13,17,25,29, 2,6,21,27,31,-17, - 3,7,14,18,33,-29, 4,8,22,35,-18,-31, - 9,15,19,26,30,-5, 10,23,28,32,-6,-19, - 11,16,20,34,-7,-30, 12,24,36,-8,-20,-32], np.int32) - nface_exp_f = PT.new_NFaceElements('NFaceElements', erange=nface_er_exp, eso=nface_eso_exp, ec=nface_ec_exp) - nface_exp = nface_exp_f - - NGT.pe_to_nface(zone, remove_PE=True) - nface = PT.Zone.NFaceNode(zone) - - assert PT.is_same_tree(nface, nface_exp) - assert PT.get_node_from_name(zone, "ParentElements") is None - -@pytest_parallel.mark.parallel([1]) -@pytest.mark.parametrize("rmNFace",[False, True]) -def test_nface_to_pe(rmNFace, comm): - tree = DCG.dcube_generate(3,1.,[0,0,0], comm) - zone = PT.get_all_Zone_t(tree)[0] - as_partitioned(zone) - pe_bck = PT.get_node_from_path(zone, 'NGonElements/ParentElements')[1] - NGT.pe_to_nface(zone, True) - nface_bck = PT.get_child_from_name(zone, 'NFaceElements') - - NGT.nface_to_pe(zone, rmNFace) - - assert (PT.get_node_from_path(zone, 'NGonElements/ParentElements')[1] == pe_bck).all() - nface_cur = PT.get_child_from_name(zone, 'NFaceElements') - if rmNFace: - assert nface_cur is None - else: - assert PT.is_same_tree(nface_bck, nface_cur) diff --git a/maia/algo/part/part_algo.pybind.cpp b/maia/algo/part/part_algo.pybind.cpp deleted file mode 100644 index 8f158660..00000000 --- a/maia/algo/part/part_algo.pybind.cpp +++ /dev/null @@ -1,159 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/algo/part/gcs_ghosts/gcs_only_for_ghosts.hpp" -#include "maia/__old/transform/remove_ghost_info.hpp" -#include "cpp_cgns/interop/pycgns_converter.hpp" -#include "maia/utils/parallel/mpi4py.hpp" -#else //C++==17 -#endif //C++>17 -#include "maia/algo/part/geometry/geometry.pybind.hpp" -#include "maia/algo/part/ngon_tools/ngon_tools.pybind.hpp" -#include "maia/algo/part/cgns_registry/cgns_registry.pybind.hpp" -#include "maia/algo/part/part_algo.pybind.hpp" -#include "maia/utils/pybind_utils.hpp" - -#include -#include -#include - -namespace py = pybind11; - -void -enforce_pe_left_parent( - py::array_t& np_face_vtx_idx, - py::array_t& np_face_vtx, - py::array_t& np_pe, - std::optional> &np_cell_face_idx, - std::optional> &np_cell_face -) -{ - int n_face = np_pe.size()/2; - auto pe_ptr = np_pe.template mutable_unchecked<2>(); - auto face_vtx_idx = make_raw_view(np_face_vtx_idx); - auto face_vtx = make_raw_view(np_face_vtx); - int *cell_face_idx = nullptr; - int *cell_face = nullptr; - if (np_cell_face_idx.has_value()) { //Take value if not None - cell_face_idx = make_raw_view(np_cell_face_idx.value()); - cell_face = make_raw_view(np_cell_face.value()); - } - - int f_shift(0); // To go back to local cell numbering if ngons are before nface - int c_shift(0); // To go back to local face numbering if nface are before ngons - if (n_face > 0) { - if (std::max(pe_ptr(0,0), pe_ptr(0,1)) > n_face ) { //NGons are first - f_shift = n_face; - } - else if (cell_face != nullptr) { //NFace are first - c_shift = np_cell_face_idx.value().size() - 1; // This is n_cell - } - } - - for(int i_face = 0; i_face < n_face; ++i_face) { - if (pe_ptr(i_face, 0) == 0) { - // Swap pe - pe_ptr(i_face, 0) = pe_ptr(i_face, 1); - pe_ptr(i_face, 1) = 0; - - // Change sign in cell_face - if (cell_face_idx != nullptr) { - int i_cell = pe_ptr(i_face, 0) - f_shift - 1; - int face_id = i_face + c_shift + 1; - int* begin = &cell_face[cell_face_idx[i_cell]]; - int* end = &cell_face[cell_face_idx[i_cell+1]]; - int *find = std::find_if(begin, end, [face_id](int x) {return abs(x) == face_id;}); - if (find != end) { - *find *= - 1; - } - } - - //Swap vertices - std::reverse(&face_vtx[face_vtx_idx[i_face]], &face_vtx[face_vtx_idx[i_face+1]]); - } - } -} - -#if __cplusplus > 201703L -template auto -apply_cpp_cgns_function_to_py_base(F&& f) { - return [&f](py::list py_base) { - cgns::tree base = cgns::to_cpp_tree(py_base); - f(base); - update_py_tree(std::move(base),py_base); - }; -} -template auto -apply_cpp_cgns_par_function_to_py_base(F&& f) { - return [&f](py::list py_base, py::object mpi4py_comm) { - cgns::tree base = cgns::to_cpp_tree(py_base); - MPI_Comm comm = maia::mpi4py_comm_to_comm(mpi4py_comm); - f(base,comm); - update_py_tree(std::move(base),py_base); - }; -} -const auto gcs_only_for_ghosts = apply_cpp_cgns_function_to_py_base(cgns::gcs_only_for_ghosts); -const auto remove_ghost_info = apply_cpp_cgns_par_function_to_py_base(maia::remove_ghost_info); -#else //C++==17 -#endif //C++>17 - - -void register_part_algo_module(py::module_& parent) { - - py::module_ m = parent.def_submodule("part_algo"); - - m.doc() = "pybind11 part_algo module"; // optional module docstring - - m.def("enforce_pe_left_parent", &enforce_pe_left_parent, - py::arg("ngon_eso").noconvert(), - py::arg("ngon_ec").noconvert(), - py::arg("ngon_pe").noconvert(), - py::arg("nface_eso").noconvert() = py::none(), - py::arg("nface_ec").noconvert() = py::none()); - - m.def("local_pe_to_local_cellface", &local_pe_to_local_cellface, - py::arg("local_pe").noconvert()); - m.def("local_pe_to_local_cellface", &local_pe_to_local_cellface, - py::arg("local_pe").noconvert()); - m.def("local_cellface_to_local_pe", &local_cellface_to_local_pe, - py::arg("np_cell_face_idx").noconvert(), - py::arg("np_cell_face").noconvert()); - - m.def("compute_center_cell_u", &compute_center_cell_u, - py::arg("n_cell").noconvert(), - py::arg("np_cx").noconvert(), - py::arg("np_cy").noconvert(), - py::arg("np_cz").noconvert(), - py::arg("np_face_vtx").noconvert(), - py::arg("np_face_vtx_idx").noconvert(), - py::arg("np_parent_elemnts").noconvert()); - - m.def("compute_center_cell_s", &compute_center_cell_s, - py::arg("nx").noconvert(), - py::arg("ny").noconvert(), - py::arg("nz").noconvert(), - py::arg("np_cx").noconvert(), - py::arg("np_cy").noconvert(), - py::arg("np_cz").noconvert()); - - m.def("compute_center_face_s", &compute_center_face_s, - py::arg("nx").noconvert(), - py::arg("ny").noconvert(), - py::arg("nz").noconvert(), - py::arg("np_cx").noconvert(), - py::arg("np_cy").noconvert(), - py::arg("np_cz").noconvert()); - - m.def("compute_face_normal_u", &compute_face_normal_u, - py::arg("np_face_vtx_idx").noconvert(), - py::arg("np_cx").noconvert(), - py::arg("np_cy").noconvert(), - py::arg("np_cz").noconvert()); - - #if __cplusplus > 201703L - m.def("gcs_only_for_ghosts" , gcs_only_for_ghosts , "For GridConnectivities, keep only in the PointList the ones that are ghosts"); - m.def("remove_ghost_info" , remove_ghost_info , "Remove ghost nodes and ghost elements of base"); - #else //C++==17 - #endif //C++>17 - - register_cgns_registry_module(m); - -} diff --git a/maia/algo/part/part_algo.pybind.hpp b/maia/algo/part/part_algo.pybind.hpp deleted file mode 100644 index 1b92ae0d..00000000 --- a/maia/algo/part/part_algo.pybind.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#include - -void register_part_algo_module(pybind11::module_& parent); - diff --git a/maia/algo/part/point_cloud_utils.py b/maia/algo/part/point_cloud_utils.py deleted file mode 100644 index b901ee9f..00000000 --- a/maia/algo/part/point_cloud_utils.py +++ /dev/null @@ -1,84 +0,0 @@ -import numpy as np - -import Pypdm.Pypdm as PDM -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import np_utils, as_pdm_gnum, layouts - -from .geometry import compute_cell_center -from .multidom_gnum import _get_shifted_arrays - -def _get_zone_ln_to_gn_from_loc(zone, location): - """ Wrapper to get the expected lngn value """ - _loc = location.replace('Center', '') - ln_to_gn = as_pdm_gnum(PT.get_value(MT.getGlobalNumbering(zone, _loc))) - return ln_to_gn - -def get_point_cloud(zone, location='CellCenter'): - """ - If location == Vertex, return the (interlaced) coordinates of vertices - and vertex global numbering of a partitioned zone - If location == Center, compute and return the (interlaced) coordinates of - cell centers and cell global numbering of a partitioned zone - """ - if location == 'Vertex': - coords = [c.reshape(-1, order='F') for c in PT.Zone.coordinates(zone)] - vtx_coords = np_utils.interweave_arrays(coords) - vtx_ln_to_gn = _get_zone_ln_to_gn_from_loc(zone, location) - return vtx_coords, vtx_ln_to_gn - - elif location == 'CellCenter': - cell_ln_to_gn = _get_zone_ln_to_gn_from_loc(zone, location) - center_cell = compute_cell_center(zone) - return center_cell, cell_ln_to_gn - - else: #Try to catch a container with the given name - container = PT.get_child_from_name(zone, location) - if container: - coords = [PT.get_value(c).reshape(-1, order='F') for c in PT.get_children_from_name(container, 'Coordinate*')] - int_coords = np_utils.interweave_arrays(coords) - ln_to_gn = _get_zone_ln_to_gn_from_loc(zone, PT.Subset.GridLocation(container)) - return int_coords, ln_to_gn - - raise RuntimeError("Unknow location or node") - -def get_shifted_point_clouds(parts_per_dom, location, comm): - """ Wraps get_point_cloud around multiple domains, - shifting lngn with previous values""" - coords_per_dom = [] - lngn_per_dom = [] - for part_zones in parts_per_dom: - point_clouds_dom = [get_point_cloud(part, location) for part in part_zones] - coords_per_dom.append([pc[0] for pc in point_clouds_dom]) - lngn_per_dom.append([pc[1] for pc in point_clouds_dom]) - - offset, shifted_lgns = _get_shifted_arrays(lngn_per_dom, comm) - - clouds_per_dom = [] - for dom_coords, dom_lngns in zip(coords_per_dom, shifted_lgns): - clouds_per_dom.append(list(zip(dom_coords, dom_lngns))) - return offset, clouds_per_dom - -def extract_sub_cloud(coords, lngn, indices): - """ - Extract coordinates and lngn from a list of indices, starting at 0. - """ - sub_lngn = layouts.extract_from_indices(lngn , indices, 1, 0) - sub_coords = layouts.extract_from_indices(coords, indices, 3, 0) - return sub_coords, sub_lngn - -def create_sub_numbering(lngn_l, comm): - """ - Create a new compact, starting at 1 numbering from a list of - gnums. - """ - n_part = len(lngn_l) - gen_gnum = PDM.GlobalNumbering(3, n_part, 0, 0., comm) - - for i_part, lngn in enumerate(lngn_l): - gen_gnum.set_from_parent(i_part, lngn) - - gen_gnum.compute() - - return [gen_gnum.get(i_part) for i_part in range(n_part)] diff --git a/maia/algo/part/test/test_closest_points.py b/maia/algo/part/test/test_closest_points.py deleted file mode 100644 index ae5433c8..00000000 --- a/maia/algo/part/test/test_closest_points.py +++ /dev/null @@ -1,124 +0,0 @@ -import os -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.factory import dcube_generator as DCG -from maia.factory import partition_dist_tree - -from maia.utils import test_utils as TU -from maia.algo.part import closest_points as CLO - -@pytest_parallel.mark.parallel(2) -class Test_closest_points: - src_clouds_per_rank = { - 0 : [(np.array([.25,.25,.75, .75,.25,75, .25,.75,.75, .75,.75,.75]), np.array([5,6,7,8], pdm_gnum_dtype))], - 1 : [(np.array([.25,.25,.25, .25,.75,.25]), np.array([1,3], pdm_gnum_dtype)), - (np.array([.75,.25,.25, .75,.75,.25]), np.array([2,4], pdm_gnum_dtype))] - } - tgt_clouds_per_rank = { - 0 : [(np.array([.6,.9,0]), np.array([2], pdm_gnum_dtype)), (np.array([1.6,.9,0]), np.array([4], pdm_gnum_dtype))], - 1 : [(np.array([.6,.9,0, .6,.9,.8, 1.2, 0.1, 0.1]), np.array([1,3,5], pdm_gnum_dtype))] - } - - def test_empty_tgt(self, comm): - src_clouds = self.src_clouds_per_rank[comm.Get_rank()] - assert CLO._closest_points(src_clouds, [], comm) == [] - - @pytest.mark.parametrize("reverse", [False, True]) - def test_standard(self, reverse, comm): - src_clouds = self.src_clouds_per_rank[comm.Get_rank()] - tgt_clouds = self.tgt_clouds_per_rank[comm.Get_rank()] - - if reverse: - tgt_data, src_data = CLO._closest_points(src_clouds, tgt_clouds, comm, n_pts=1, reverse=True) - else: - tgt_data = CLO._closest_points(src_clouds, tgt_clouds, comm, n_pts=1, reverse=False) - - if comm.Get_rank() == 0: - expected_tgt_data = [{'closest_src_gnum' : [4], 'closest_src_distance' : [0.1075]}, - {'closest_src_gnum' : [4], 'closest_src_distance' : [0.8075]}] - expected_src_data = [{'tgt_in_src_idx' : [0,0,0,0,1], 'tgt_in_src' : [3], 'tgt_in_src_dist2' : [0.0475]}] - elif comm.Get_rank() == 1: - expected_tgt_data = [{'closest_src_gnum' : [4,8,2], 'closest_src_distance' : [0.1075, 0.0475, 0.2475]}] - expected_src_data = [{'tgt_in_src_idx' : [0,0,0], 'tgt_in_src' : [], 'tgt_in_src_dist2' : []}, - {'tgt_in_src_idx' : [0,1,4], 'tgt_in_src' : [5,1,2,4], 'tgt_in_src_dist2' : [0.2475, 0.1075, 0.1075, 0.8075]}] - - for i_part, expct_data in enumerate(expected_tgt_data): - for key in expct_data: - assert np.allclose(tgt_data[i_part][key], expct_data[key]) - if reverse: - for i_part, expct_data in enumerate(expected_src_data): - for key in expct_data: - assert np.allclose(src_data[i_part][key], expct_data[key]) - - def test_mult_pts(self, comm): - src_clouds = self.src_clouds_per_rank[comm.Get_rank()] - tgt_clouds = self.tgt_clouds_per_rank[comm.Get_rank()] - - tgt_data = CLO._closest_points(src_clouds, tgt_clouds, comm, n_pts=3) - - if comm.Get_rank() == 0: - expected_tgt_data = [{'closest_src_gnum' : [2,3,4], 'closest_src_distance' : [0.5075, 0.2075, 0.1075]}, - {'closest_src_gnum' : [2,4,8], 'closest_src_distance' : [1.2075, 0.8075, 1.3075]}] - elif comm.Get_rank() == 1: - expected_tgt_data = [{'closest_src_gnum' : [2,3,4, 4,7,8, 1,2,4], - 'closest_src_distance' : [0.5075, 0.2075, 0.1075, 0.3475, 0.1475, 0.0475, 0.9475, 0.2475, 0.6475]}] - - for i_part, expct_data in enumerate(expected_tgt_data): - for key in expct_data: - assert np.allclose(tgt_data[i_part][key], expct_data[key]) - -@pytest_parallel.mark.parallel(1) -def test_closestpoint_mdom(comm): - yaml_path = os.path.join(TU.mesh_dir, 'S_twoblocks.yaml') - dtree_src = maia.io.file_to_dist_tree(yaml_path, comm) - dtree_tgt = DCG.dcube_generate(5, 4., [0.,0.,0.], comm) - maia.algo.transform_affine(dtree_tgt, translation=np.array([13.25, 2.25, 0.25])) - - tree_src = partition_dist_tree(dtree_src, comm) - tree_tgt = partition_dist_tree(dtree_tgt, comm) - tgt_parts_per_dom = [PT.get_all_Zone_t(tree_tgt)] - src_parts_per_dom = [PT.get_nodes_from_name_and_label(tree_src, 'Large*', 'Zone_t'), - PT.get_nodes_from_name_and_label(tree_src, 'Small*', 'Zone_t')] - - result, result_inv = CLO._find_closest_points( - src_parts_per_dom, tgt_parts_per_dom, 'Vertex', 'Vertex', comm, reverse=True) - _result = result[0][0] - dom1_idx = np.where(_result['domain'] == 1)[0] - dom2_idx = np.where(_result['domain'] == 2)[0] - assert dom1_idx.size == 87 #Carefull, 25 resulting points are on the interface, result can change - assert dom2_idx.size == 38 - # Gnum should have been reshifted - assert _result['closest_src_gnum'][dom1_idx].max() <= PT.Zone.n_vtx(src_parts_per_dom[0][0]) - assert _result['closest_src_gnum'][dom2_idx].max() <= PT.Zone.n_vtx(src_parts_per_dom[1][0]) - -@pytest_parallel.mark.parallel(3) -def test_closest_points(comm): - dtree_src = DCG.dcube_generate(5, 1., [0.,0.,0.], comm) - dtree_tgt = DCG.dcube_generate(4, 1., [.4,-0.01,-0.01], comm) - tree_src = partition_dist_tree(dtree_src, comm) - tree_tgt = partition_dist_tree(dtree_tgt, comm) - - tree_src_back = PT.deep_copy(tree_src) - CLO.find_closest_points(tree_src, tree_tgt, 'CellCenter', comm) - assert PT.is_same_tree(tree_src_back, tree_src) - tgt_zone = PT.get_all_Zone_t(tree_tgt)[0] - clo_node = PT.get_node_from_name_and_label(tgt_zone, 'ClosestPoint', 'DiscreteData_t') - assert clo_node is not None and PT.Subset.GridLocation(clo_node) == 'CellCenter' - assert PT.get_value(PT.get_child_from_name(clo_node, 'DomainList')) == "Base/zone" - - # Check result on dist tree to not rely on partitioning - maia.transfer.part_tree_to_dist_tree_all(dtree_tgt, tree_tgt, comm) - if comm.rank == 0: - expected_dsrc_id = np.array([3,4,4,7,8,8,15,16,16]) - elif comm.rank == 1: - expected_dsrc_id = np.array([19,20,20,23,24,24,31,32,32]) - elif comm.rank == 2: - expected_dsrc_id = np.array([51,52,52,55,56,56,63,64,64]) - assert (PT.get_node_from_name(dtree_tgt, 'SrcId')[1] == expected_dsrc_id).all() diff --git a/maia/algo/part/test/test_connectivity_transform.py b/maia/algo/part/test/test_connectivity_transform.py deleted file mode 100644 index efc6b697..00000000 --- a/maia/algo/part/test/test_connectivity_transform.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia -from maia.factory import dcube_generator as DCG -from maia.algo.part import connectivity_transform as CNT - -def as_int32(zone): - predicate = lambda n : PT.get_label(n) in ['Zone_t', 'DataArray_t', 'IndexArray_t', 'IndexRange_t'] - for array in PT.iter_nodes_from_predicate(zone, predicate, explore='deep'): - array[1] = array[1].astype(np.int32) - -@pytest_parallel.mark.parallel(1) -def test_enforce_boundary_pe_left(comm): - tree = DCG.dcube_generate(3, 1., [0., 0., 0.], comm) - zone = PT.get_all_Zone_t(tree)[0] - as_int32(zone) - maia.algo.pe_to_nface(zone, comm) - pe_node = PT.get_node_from_path(zone, 'NGonElements/ParentElements') - pe_bck = pe_node[1].copy() - zone_bck = PT.deep_copy(zone) - CNT.enforce_boundary_pe_left(zone) - assert PT.is_same_tree(zone, zone_bck) - - #Test with swapped pe - pe_node[1][2] = pe_node[1][2][::-1] - CNT.enforce_boundary_pe_left(zone) - - assert PT.get_node_from_path(zone, 'NFaceElements/ElementConnectivity')[1][12] == -29 - expt_ng_ec = PT.get_node_from_path(zone_bck, 'NGonElements/ElementConnectivity')[1].copy() - expt_ng_ec[4*2 : 4*3] = [4, 7, 8, 5] - assert (PT.get_node_from_path(zone, 'NGonElements/ElementConnectivity')[1] == expt_ng_ec).all() - - #Test with no NFace - zone = PT.deep_copy(zone_bck) - pe_node = PT.get_node_from_path(zone, 'NGonElements/ParentElements') - pe_node[1][2] = pe_node[1][2][::-1] - PT.rm_children_from_name(zone, 'NFaceElements') - CNT.enforce_boundary_pe_left(zone) - assert (PT.get_node_from_path(zone, 'NGonElements/ElementConnectivity')[1] == expt_ng_ec).all() diff --git a/maia/algo/part/test/test_connectivity_utils.py b/maia/algo/part/test/test_connectivity_utils.py deleted file mode 100644 index 1789aaaa..00000000 --- a/maia/algo/part/test/test_connectivity_utils.py +++ /dev/null @@ -1,72 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT - -import maia - -from maia.algo.part import connectivity_utils as CU - -def as_partitioned(zone): - #TODO : factorize me - PT.rm_nodes_from_name(zone, ":CGNS#Distribution") - predicate = lambda n : PT.get_label(n) in ['Zone_t', 'DataArray_t', 'IndexArray_t', 'IndexRange_t'] \ - and PT.get_value(n).dtype == np.int64 - for array in PT.iter_nodes_from_predicate(zone, predicate, explore='deep'): - array[1] = array[1].astype(np.int32) - - -@pytest_parallel.mark.parallel(1) -class Test_cell_vtx_connectivity: - - @pytest.mark.parametrize("elt_kind", ['NFACE_n' ,'Poly']) - def test_ngon3d(self, elt_kind, comm): - tree = maia.factory.generate_dist_block(3, elt_kind, comm) - zone = PT.get_all_Zone_t(tree)[0] - as_partitioned(zone) - cell_vtx_idx, cell_vtx = CU.cell_vtx_connectivity(zone) - expected_cell_vtx = np.array([1,2,4,5,10,11,13,14, 2,3,5,6,11,12,14,15, - 4,5,7,8,13,14,16,17, 5,6,8,9,14,15,17,18, - 10,11,13,14,19,20,22,23, 11,12,14,15,20,21,23,24, - 13,14,16,17,22,23,25,26, 14,15,17,18,23,24,26,27]) - assert cell_vtx_idx.size == 2**3+1 - assert (np.diff(cell_vtx_idx) == 8).all() - assert (cell_vtx == expected_cell_vtx).all() - - @pytest.mark.parametrize("elt_kind", ['TETRA_4' ,'QUAD_4']) - def test_elts(self, elt_kind, comm): - tree = maia.factory.generate_dist_block(3, elt_kind, comm) - zone = PT.get_all_Zone_t(tree)[0] - as_partitioned(zone) - dim_zone = 2 if elt_kind in ['QUAD_4'] else 3 - - cell_vtx_idx, cell_vtx = CU.cell_vtx_connectivity(zone, dim_zone) - - elt = PT.get_node_from_label(zone, 'Elements_t') - assert cell_vtx_idx.size == PT.Element.Size(elt) + 1 - assert (np.diff(cell_vtx_idx) == 4).all() - assert (cell_vtx == PT.get_node_from_name(elt, 'ElementConnectivity')[1]).all() - - def test_cell_vtx_s_2d(self, comm): - dist_tree = maia.factory.generate_dist_block([3,3,1], 'S', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - zone = PT.get_all_Zone_t(part_tree)[0] - - cell_vtx_index, cell_vtx = CU.cell_vtx_connectivity_S(zone, dim=2) - assert np.array_equal(cell_vtx, [1,2,5,4, 2,3,6,5, 4,5,8,7, 5,6,9,8]) - assert np.array_equal(cell_vtx_index, [0,4,8,12,16]) - assert cell_vtx.dtype == cell_vtx_index.dtype == np.int32 - - def test_cell_vtx_s_3d(self, comm): - dist_tree = maia.factory.generate_dist_block(3, 'S', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - zone = PT.get_all_Zone_t(part_tree)[0] - - cell_vtx_index, cell_vtx = CU.cell_vtx_connectivity_S(zone, dim=3) - assert np.array_equal(cell_vtx, [1,2,5,4,10,11,14,13, 2,3,6,5,11,12,15,14, 4,5,8,7,13,14,17,16, - 5,6,9,8,14,15,18,17, 10,11,14,13,19,20,23,22, 11,12,15,14,20,21,24,23, - 13,14,17,16,22,23,26,25, 14,15,18,17,23,24,27,26]) - assert np.array_equal(cell_vtx_index,[0,8,16,24,32,40,48,56,64]) - assert cell_vtx.dtype == cell_vtx_index.dtype == np.int32 - diff --git a/maia/algo/part/test/test_extract_boundary.py b/maia/algo/part/test/test_extract_boundary.py deleted file mode 100644 index 77226f8f..00000000 --- a/maia/algo/part/test/test_extract_boundary.py +++ /dev/null @@ -1,172 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -dtype = 'I4' if pdm_gnum_dtype == np.int32 else 'I8' -from maia.factory.dcube_generator import dcube_generate -from maia.pytree.yaml import parse_yaml_cgns - -from maia.algo.part import extract_boundary as EXB - -def test_pr_to_face_pl(): - n_vtx = np.array([4,5,5], np.int32) - pl = EXB._pr_to_face_pl(n_vtx, np.array([[1,1], [1,3], [1,3]], order='F'), 'Vertex') - assert (pl == [[1,5,17,21]]).all() - pl = EXB._pr_to_face_pl(n_vtx, np.array([[1,4], [1,5], [5,5]], order='F'), 'Vertex') - assert (pl == [[173,174,175,176,177,178,179,180,181,182,183,184]]).all() - pl = EXB._pr_to_face_pl(n_vtx, np.array([[1,3], [1,4], [5,5]], order='F'), 'FaceCenter') - assert (pl == [[173,174,175,176,177,178,179,180,181,182,183,184]]).all() - pl = EXB._pr_to_face_pl(n_vtx, np.array([[1,3], [1,4], [4,4]], order='F'), 'CellCenter') - assert (pl == [[173,174,175,176,177,178,179,180,181,182,183,184]]).all() - -def test_extract_sub_connectivity(): - face_vtx_idx = np.array([0, 4, 9, 12]) - face_vtx = np.array([104, 105, 115, 114, 105, 104, 103, 102, 101, 35,34,114]) - - sub_face_vtx_idx, sub_face_vtx, vtx_ids = EXB._extract_sub_connectivity(face_vtx_idx, face_vtx, np.array([1,2,3])) - assert (sub_face_vtx_idx == [0,4,9,12]).all() - assert (sub_face_vtx == [6,7,9,8, 7,6,5,4,3, 2,1,8]).all() - assert (vtx_ids == [34,35,101,102,103,104,105,114,115]).all() - - sub_face_vtx_idx, sub_face_vtx, vtx_ids = EXB._extract_sub_connectivity(face_vtx_idx, face_vtx, np.array([1,3])) - assert (sub_face_vtx_idx == [0,4,7]).all() - assert (sub_face_vtx == [3,4,6,5, 2,1,5]).all() - assert (vtx_ids == [34,35,104,105,114,115]).all() - -@pytest_parallel.mark.parallel(1) -def test_extract_faces_mesh(comm): - # Test U - tree = dcube_generate(3, 1., [0,0,0], comm) - PT.rm_nodes_from_name(tree, ':CGNS#Distribution') - zoneU = PT.get_all_Zone_t(tree)[0] - - cx, cy, cz, face_vtx_idx, face_vtx, vtx_ids = EXB.extract_faces_mesh(zoneU, np.array([21,22,23,24])) - - assert len(cx) == 9 - assert len(face_vtx_idx) == 4 + 1 - assert (cx == [1,1,1,1,1,1,1,1,1]).all() - assert (cy == [0,.5,1,0,.5,1,0,.5,1]).all() - assert (cz == [0,0,0,.5,.5,.5,1,1,1]).all() - assert (face_vtx == [4,5,2,1,5,6,3,2,7,8,5,4,8,9,6,5]).all() - assert (vtx_ids == [3,6,9,12,15,18,21,24,27]).all() - - # Test S - cx_s = PT.get_node_from_name(zoneU, 'CoordinateX')[1].reshape((3,3,3), order='F') - cy_s = PT.get_node_from_name(zoneU, 'CoordinateY')[1].reshape((3,3,3), order='F') - cz_s = PT.get_node_from_name(zoneU, 'CoordinateZ')[1].reshape((3,3,3), order='F') - - zoneS = PT.new_Zone(size=[[3,2,0], [3,2,0], [3,2,0]], type='Structured') - grid_coords = PT.new_GridCoordinates(parent=zoneS) - PT.new_DataArray('CoordinateX', cx_s, parent=grid_coords) - PT.new_DataArray('CoordinateY', cy_s, parent=grid_coords) - PT.new_DataArray('CoordinateZ', cz_s, parent=grid_coords) - - # PDM does not use the same ordering for faces, in our structured convention xmax boundary - # would be faces 3,6,9,12 - cx, cy, cz, face_vtx_idx, face_vtx, vtx_ids = EXB.extract_faces_mesh(zoneS, np.array([3,6,9,12])) - assert (cx == [1,1,1,1,1,1,1,1,1]).all() - assert (cy == [0,.5,1,0,.5,1,0,.5,1]).all() - assert (cz == [0,0,0,.5,.5,.5,1,1,1]).all() - assert (face_vtx == [1,2,5,4,2,3,6,5,4,5,8,7,5,6,9,8]).all() - assert (vtx_ids == [3,6,9,12,15,18,21,24,27]).all() - - -@pytest_parallel.mark.parallel(2) -def test_extract_surf_from_bc(comm): - #We dont put GlobalNumbering for BCs, since its not needed, but we should - part_0 = f""" - ZoneU Zone_t [[18,4,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t [0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1]: - CoordinateY DataArray_t [0, 0, 0, 0.5, 0.5, 0.5, 1, 1, 1, 0, 0, 0, 0.5, 0.5, 0.5, 1, 1, 1]: - CoordinateZ DataArray_t [0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]: - NGon Elements_t [22,0]: - ElementRange IndexRange_t [1,20]: - ElementConnectivity DataArray_t: - I4 : [ 2, 5, 4, 1, 3, 6, 5, 2, 5, 8, 7, 4, 6, 9, 8, 5, 10, - 13, 14, 11, 11, 14, 15, 12, 13, 16, 17, 14, 14, 17, 18, 15, 1, 4, - 13, 10, 4, 7, 16, 13, 11, 14, 5, 2, 14, 17, 8, 5, 12, 15, 6, - 3, 15, 18, 9, 6, 10, 11, 2, 1, 11, 12, 3, 2, 4, 5, 14, 13, - 5, 6, 15, 14, 7, 8, 17, 16, 8, 9, 18, 17] - ElementStartOffset DataArray_t I4 [0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80]: - ParentElements DataArray_t: - I4 : [[1, 0], [2, 0], [4, 0], [3, 0], [1, 0], [2, 0], [4, 0], [3, 0], [1, 0], [4, 0], - [1, 2], [4, 3], [2, 0], [3, 0], [1, 0], [2, 0], [1, 4], [2, 3], [4, 0], [3, 0]] - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [1,2,3,4,5,6,7,8,13,14,17,18,21,22,25,27,29,31,33,35]: - ZoneBC ZoneBC_t: - bcA BC_t "FamilySpecified": - PointList IndexArray_t [[15,16]]: - GridLocation GridLocation_t "FaceCenter": - FamilyName FamilyName_t "WALL": - bcB BC_t "FamilySpecified": - PointList IndexArray_t [[1,3,4,2]]: - GridLocation GridLocation_t "FaceCenter": - FamilyName FamilyName_t "NOTWALL": - otherBC BC_t "FamilySpecified": - PointList IndexArray_t [[9,10]]: - GridLocation GridLocation_t "FaceCenter": - FamilyName FamilyName_t "WALL": - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t {dtype} [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]: - Cell DataArray_t {dtype} [1,2,3,4]: - """ - part_1 = f""" - ZoneU Zone_t [[18,4,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t [0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1]: - CoordinateY DataArray_t [0, 0, 0, 0.5, 0.5, 0.5, 1, 1, 1, 0, 0, 0, 0.5, 0.5, 0.5, 1, 1, 1]: - CoordinateZ DataArray_t [1, 1, 1, 1, 1, 1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]: - NGon Elements_t [22,0]: - ElementRange IndexRange_t [1,20]: - ElementConnectivity DataArray_t: - I4 : [10, 11, 14, 13, 11, 12, 15, 14, 13, 14, 17, 16, 14, 15, 18, 17, 1, - 4, 5, 2, 2, 5, 6, 3, 4, 7, 8, 5, 5, 8, 9, 6, 10, 13, - 4, 1, 13, 16, 7, 4, 2, 5, 14, 11, 5, 8, 17, 14, 3, 6, 15, - 12, 6, 9, 18, 15, 1, 2, 11, 10, 2, 3, 12, 11, 13, 14, 5, 4, - 14, 15, 6, 5, 16, 17, 8, 7, 17, 18, 9, 8] - ElementStartOffset DataArray_t I4 [0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80]: - ParentElements DataArray_t: - I4 : [[1, 0], [2, 0], [3, 0], [4, 0], [1, 0], [2, 0], [3, 0], [4, 0], [1, 0], [3, 0], - [1, 2], [3, 4], [2, 0], [4, 0], [1, 0], [2, 0], [1, 3], [2, 4], [3, 0], [4, 0]] - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [5,6,7,8,9,10,11,12,15,16,19,20,23,24,26,28,30,32,34,36]: - ZoneBC ZoneBC_t: - bcA BC_t "FamilySpecified": - PointList IndexArray_t [[15,16]]: - GridLocation GridLocation_t "FaceCenter": - FamilyName FamilyName_t "WALL": - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t {dtype} [19,20,21,22,23,24,25,26,27,10,11,12,13,14,15,16,17,18]: - Cell DataArray_t {dtype} [5,6,7,8]: - """ - if comm.Get_rank() == 0: - part_zones = [parse_yaml_cgns.to_node(part_0)] - bc_pl = np.array([15,16,9,10]) - expt_face_lngn = [3,5,1,2] - expt_vtx_lngn = [1,2,3,4,5,6,7,8,9,10] - elif comm.Get_rank() == 1: - part_zones = [parse_yaml_cgns.to_node(part_1)] - bc_pl = np.array([15,16]) - expt_face_lngn = [4,6] - expt_vtx_lngn = [11,12,13,6,7,8] - - bc_predicate = lambda n: PT.get_value(PT.get_child_from_name(n, 'FamilyName')) == 'WALL' - - bc_face_vtx, bc_face_vtx_idx, bc_face_lngn, bc_coords, bc_vtx_lngn = \ - EXB.extract_surf_from_bc(part_zones, bc_predicate, comm) - - - assert len(bc_face_vtx) == len(bc_face_vtx_idx) == len(bc_face_lngn) == len(bc_coords) == len(bc_vtx_lngn) == 1 - - cx, cy, cz, expt_bc_face_vtx_idx, expt_bc_face_vtx, _ = EXB.extract_faces_mesh(part_zones[0], bc_pl) - assert (bc_face_vtx_idx[0] == expt_bc_face_vtx_idx).all() - assert (bc_face_vtx[0] == expt_bc_face_vtx).all() - assert (bc_coords[0] == np.array([cx,cy,cz]).reshape(-1, order='F')).all() - assert (bc_face_lngn[0] == expt_face_lngn).all() - assert (bc_vtx_lngn[0] == expt_vtx_lngn).all() diff --git a/maia/algo/part/test/test_extract_part.py b/maia/algo/part/test/test_extract_part.py deleted file mode 100644 index 9024de8b..00000000 --- a/maia/algo/part/test/test_extract_part.py +++ /dev/null @@ -1,281 +0,0 @@ -from mpi4py import MPI -import pytest -import pytest_parallel -import numpy as np - -import maia -import maia.pytree as PT -from maia.utils import s_numbering - -from maia.algo.part import extract_part as EP - -def sample_part_tree(cgns_name, comm, bc_loc='Vertex'): - if cgns_name=='Structured': - dist_tree = maia.factory.dcube_generator.dcube_struct_generate(5, 1., [0.,0.,0.], comm, bc_location=bc_loc) - else: - dist_tree = maia.factory.generate_dist_block(3, "Poly", comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - return part_tree - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("location", ['Vertex','FaceCenter','CellCenter']) -def test_extract_part_simple_u(location, comm): - part_tree = sample_part_tree('Poly', comm) - - pl = np.array([[1,2]], np.int32) - if location=='CellCenter': - pl += PT.Zone.n_face(PT.get_all_Zone_t(part_tree)[0]) - - ex_zone, ptp_data = EP.extract_part_one_domain_u(PT.get_all_Zone_t(part_tree), \ - [pl], location, comm) - assert len(ex_zone)==1 - ex_zone = ex_zone[0] - if location=='Vertex': - assert PT.Zone.n_vtx(ex_zone) == 2 - assert PT.Zone.n_cell(ex_zone) == 0 - elif location=='FaceCenter': - assert PT.Zone.n_vtx(ex_zone) == 6 - assert PT.Zone.n_cell(ex_zone) == 2 - elif location=='CellCenter': - assert PT.Zone.n_vtx(ex_zone) == 12 - assert PT.Zone.n_cell(ex_zone) == 2 - assert ptp_data['part_to_part']["CellCenter"] is not None - - assert ptp_data['part_to_part']["Vertex"] is not None - -@pytest_parallel.mark.parallel([2]) -@pytest.mark.parametrize("bc_loc" , ['Vertex','FaceCenter']) -def test_extract_part_simple_s(bc_loc, comm): - part_tree = sample_part_tree('Structured', comm, bc_loc) - - location = 'Vertex' if bc_loc=='Vertex' else 'KFaceCenter' - pr = PT.get_value(PT.get_child_from_predicates(part_tree, f'CGNSBase_t/Zone_t/ZoneBC_t/Zmax/PointRange')) - ex_zones, etb_zones = EP.extract_part_one_domain_s(PT.get_all_Zone_t(part_tree), \ - [pr], location, comm) - - assert PT.Zone.n_vtx(ex_zones[0]) == 15 - assert PT.Zone.n_cell(ex_zones[0]) == 8 - -@pytest_parallel.mark.parallel([1,3]) -@pytest.mark.parametrize("bc_loc" , ['Vertex','FaceCenter']) -def test_extract_part_simple_s_from_api(bc_loc, comm): - - dist_tree = maia.factory.dcube_generator.dcube_struct_generate(10, 1., [0.,0.,0.], comm, bc_location=bc_loc) - part_opts = maia.factory.partitioning.compute_regular_weights(dist_tree, comm, n_part=4) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm, zone_to_parts=part_opts) - - # > Initialize flow solution - for part_zone in PT.get_all_Zone_t(part_tree): - cx, _, _ = PT.Zone.coordinates(part_zone) - PT.new_FlowSolution('FlowSol#Vtx', loc='Vertex', fields={'cx':cx}, parent=part_zone) - - for bc_name in ['Xmin','Xmax','Ymin','Ymax','Zmin','Zmax']: - extract_part_tree = EP.extract_part_from_bc_name(part_tree, bc_name, comm, containers_name=['FlowSol#Vtx']) - extract_dist_tree = maia.factory.recover_dist_tree(extract_part_tree, comm) - extract_dist_zone = PT.get_all_Zone_t(extract_dist_tree)[0] - assert PT.Zone.n_vtx( extract_dist_zone)==100 - assert PT.Zone.n_cell(extract_dist_zone)==81 - coord_x,_,_ = PT.Zone.coordinates(extract_dist_zone) - field_x = PT.get_node_from_path(extract_dist_zone, 'FlowSol#Vtx/cx')[1] - assert np.array_equal(coord_x, field_x) - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("cgns_name" , ['Structured','Poly']) -def test_extract_part_obj(cgns_name, comm): - if cgns_name=='Structured': - dist_tree = maia.factory.dcube_generator.dcube_struct_generate(3, 1., [0.,0.,0.], comm) - else: - dist_tree = maia.factory.generate_dist_block(3, "Poly", comm) - zone_to_parts = maia.factory.partitioning.compute_regular_weights(\ - dist_tree, comm, 2*(comm.Get_rank() == 1)) #2 parts on proc 1, 0 on proc 0 - part_tree = maia.factory.partition_dist_tree(dist_tree, comm, zone_to_parts=zone_to_parts) - - if comm.Get_rank() == 0: - pl = [] - else: - pl = [np.array([[1,2],[1,1],[1,3]], np.int32), np.array([[1,2],[1,1],[1,3]], np.int32)] if cgns_name=='Structured' else\ - [np.array([[1,2]], np.int32), np.array([[1,2]], np.int32)] - loc = 'Vertex' if cgns_name=='Structured' else 'FaceCenter' - extractor = EP.Extractor(part_tree, [pl], loc, comm) - extracted_tree = extractor.get_extract_part_tree() - - if cgns_name=='Structured': - if comm.rank==0: - assert len(PT.get_all_Zone_t(extracted_tree)) == 0 - else: - assert len(PT.get_all_Zone_t(extracted_tree)) == 2 - assert (PT.get_all_CGNSBase_t(extracted_tree)[0][1] == [2,3]).all() - else: - assert len(PT.get_all_Zone_t(extracted_tree)) == 1 - assert (PT.get_all_CGNSBase_t(extracted_tree)[0][1] == [2,3]).all() - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("cgns_name" , ['Structured','Poly']) -@pytest.mark.parametrize('partial', [False, True]) -def test_exch_field(cgns_name, partial, comm): - part_tree = sample_part_tree(cgns_name, comm) - - # Add field - for zone in PT.get_all_Zone_t(part_tree): - n_vtx = PT.Zone.n_vtx(zone) - gnum = PT.maia.getGlobalNumbering(zone, 'Vertex')[1] - if partial: #Take one over two - if cgns_name=='Structured': - pr = np.array([[1,3],[1,1],[1,5]], np.int32) - i_ar = np.arange(min(pr[0]), max(pr[0])+1) - j_ar = np.arange(min(pr[1]), max(pr[1])+1).reshape(-1,1) - k_ar = np.arange(min(pr[2]), max(pr[2])+1).reshape(-1,1,1) - pl = s_numbering.ijk_to_index_from_loc(i_ar, j_ar, k_ar, 'Vertex', PT.Zone.VertexSize(zone)).flatten() - PT.new_ZoneSubRegion('FlowSol', loc="Vertex", point_range=pr, fields={'gnum': gnum[pl-1]}, parent=zone) - else: - pl = np.arange(1,n_vtx+1)[::2].astype(np.int32).reshape((1,-1)) - PT.new_ZoneSubRegion('FlowSol', loc="Vertex", point_list=pl, fields={'gnum': gnum[::2]}, parent=zone) - else: - fld = gnum.reshape(PT.Zone.VertexSize(zone), order='F') if cgns_name=='Structured' else gnum - PT.new_FlowSolution('FlowSol', loc="Vertex", fields={'gnum': fld}, parent=zone) - - extractor = EP.Extractor(part_tree, [[np.array([[1,3],[1,5],[1,1]], np.int32)]], "Vertex", comm) - extractor.exchange_fields(['FlowSol']) - extr_tree = extractor.get_extract_part_tree() - - extr_zone = PT.get_all_Zone_t(extr_tree)[0] - extr_sol = PT.get_node_from_name(extr_tree, 'FlowSol') - assert PT.Subset.GridLocation(extr_sol) == 'Vertex' - data = PT.get_node_from_name(extr_sol, 'gnum')[1] - if partial: - assert PT.get_label(extr_sol) == 'ZoneSubRegion_t' - if cgns_name=='Structured': - pr = PT.get_node_from_name(extr_sol, 'PointRange')[1] - i_ar = np.arange(min(pr[0]), max(pr[0])+1) - j_ar = np.arange(min(pr[1]), max(pr[1])+1).reshape(-1,1) - k_ar = np.arange(min(pr[2]), max(pr[2])+1).reshape(-1,1,1) - pl = s_numbering.ijk_to_index_from_loc(i_ar, j_ar, k_ar, 'Vertex', PT.Zone.VertexSize(extr_zone)).flatten() - lnum = extractor.exch_tool_box['Base/zone'][PT.get_name(extr_zone)]['parent_lnum_vtx'] - lnum = lnum[pl-1] - zone = PT.get_all_Zone_t(part_tree)[0] - gnum = PT.maia.getGlobalNumbering(zone, 'Vertex')[1] - gnum = gnum[lnum-1] - else: - pl = PT.get_node_from_name(extr_sol, 'PointList')[1][0] - gnum = extractor.exch_tool_box['Base/zone']['parent_elt']['Vertex'][pl-1] - assert np.array_equal(gnum, data) - else: - assert PT.get_label(extr_sol) == 'FlowSolution_t' - if cgns_name=='Structured': - lnum = extractor.exch_tool_box['Base/zone'][PT.get_name(extr_zone)]['parent_lnum_vtx'] - zone = PT.get_all_Zone_t(part_tree)[0] - gnum = PT.maia.getGlobalNumbering(zone, 'Vertex')[1] - gnum = gnum[lnum-1].reshape(PT.Zone.VertexSize(extr_zone), order='F') - else: - gnum = extractor.exch_tool_box['Base/zone']['parent_elt']['Vertex'] - assert np.array_equal(data,gnum) - -@pytest_parallel.mark.parallel(2) -def test_exch_field_from_bc_zsr(comm): - part_tree = sample_part_tree('Poly', comm) - - # Add field - for zone in PT.get_all_Zone_t(part_tree): - gnum = PT.maia.getGlobalNumbering(PT.get_node_from_name(zone, 'NGonElements'), 'Element')[1] - bc_n = PT.get_child_from_predicates(zone, 'ZoneBC_t/Xmin') - if bc_n is not None: - bc_pl = PT.get_value(PT.get_node_from_name(bc_n, "PointList")) - bc_gnum = gnum[bc_pl[0]-1] - PT.new_ZoneSubRegion('ZSR_Xmin', bc_name="Xmin", fields={'gnum': bc_gnum}, parent=zone) - - extractor = EP.Extractor(part_tree, [[bc_pl]], "FaceCenter", comm) - extractor.exchange_fields(['ZSR_Xmin']) - extr_tree = extractor.get_extract_part_tree() - - extr_sol = PT.get_node_from_name(extr_tree, 'ZSR_Xmin') - assert PT.get_label(extr_sol) == 'ZoneSubRegion_t' - assert PT.Subset.GridLocation(extr_sol) == 'CellCenter' - pl = PT.get_node_from_name(extr_sol, 'PointList')[1][0] - data = PT.get_node_from_name(extr_sol, 'gnum')[1] - assert np.array_equal(extractor.exch_tool_box['Base/zone']['parent_elt']['FaceCenter'][pl-1], data) - - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("cgns_name" , ['Structured','Poly']) -def test_zsr_api(cgns_name, comm): - dist_tree = maia.factory.generate_dist_block(4, cgns_name, comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - if comm.rank != 1: - for zone in PT.get_all_Zone_t(part_tree): - if cgns_name=="Structured": - pr = np.array([[1,1],[1,1],[1,2]], dtype=np.int32) - zsr_n = PT.new_ZoneSubRegion('ToExtract', loc='CellCenter', point_range=pr, parent=zone) - else: - n_face = PT.Zone.n_face(zone) - pl = np.array([1,2], dtype=np.int32).reshape((1,-1)) + n_face - zsr_n = PT.new_ZoneSubRegion('ToExtract', loc='CellCenter', point_list=pl, parent=zone) - extracted_tree = EP.extract_part_from_zsr(part_tree, 'ToExtract', comm) - - zone_n = PT.get_all_Zone_t(extracted_tree) - n_cell_extr = PT.Zone.n_cell(zone_n[0]) if len(zone_n)==1 else 0 - assert comm.allreduce(n_cell_extr, op=MPI.SUM) == 4 - - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("cgns_name" , ['Structured','Poly']) -def test_bc_name_api(cgns_name, comm): - dist_tree = maia.factory.generate_dist_block(4, cgns_name, comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - extracted_tree = EP.extract_part_from_bc_name(part_tree, 'Xmin', comm) - - zone_n = PT.get_all_Zone_t(extracted_tree) - n_cell_extr = PT.Zone.n_cell(zone_n[0]) if len(zone_n)==1 else 0 - assert comm.allreduce(n_cell_extr, op=MPI.SUM) == 9 - - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("dim_zsr", ["FaceCenter", "CellCenter"]) -def test_from_fam_api(dim_zsr, comm): - dist_tree = maia.factory.generate_dist_block(4, "Poly", comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - part_zone = PT.get_node_from_label(part_tree, 'Zone_t') - bc_n = PT.get_node_from_name(part_zone, 'Xmin') - PT.new_node('FamilyName', label='FamilyName_t', value='EXTRACT', parent=bc_n) - - if dim_zsr=="FaceCenter": - if PT.get_node_from_name(part_zone, 'Xmax') is not None: - zsr_n = PT.new_ZoneSubRegion("ZSR", bc_name='Xmax', family='EXTRACT', parent=part_zone) - extracted_tree = EP.extract_part_from_family(part_tree, 'EXTRACT', comm) - n_cell_extr = PT.Zone.n_cell(PT.get_all_Zone_t(extracted_tree)[0]) - assert comm.allreduce(n_cell_extr, op=MPI.SUM) == 18 - - elif dim_zsr=="CellCenter": - zsr_n = PT.new_ZoneSubRegion("ZSR", loc=dim_zsr, - point_list=np.array([[1]], dtype=np.int32), family='EXTRACT', parent=part_zone) - with pytest.raises(ValueError): - extracted_tree = EP.extract_part_from_family(part_tree, 'EXTRACT', comm) - - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("valid", [False, True]) -def test_from_fam_zsr_api(valid, comm): - dist_tree = maia.factory.generate_dist_block(4, "Poly", comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - part_zone = PT.get_node_from_label(part_tree, 'Zone_t') - - bc_n = PT.get_node_from_name(part_zone, 'Xmin') - if bc_n is not None : - zsr_n = PT.new_ZoneSubRegion("ZSR_Xmin", bc_name='Xmin', family='EXTRACT', parent=part_zone) - - bc_n = PT.get_node_from_name(part_zone, 'Xmax') - if bc_n is not None : - zsr_n = PT.new_ZoneSubRegion("ZSR_Xmax", bc_name='Xmax', family='EXTRACT', parent=part_zone) - if not valid: - PT.set_value(PT.get_child_from_name(bc_n ,'GridLocation'), 'Vertex') - - if valid: - extracted_tree = EP.extract_part_from_family(part_tree, 'EXTRACT', comm) - n_cell_extr = PT.Zone.n_cell(PT.get_all_Zone_t(extracted_tree)[0]) - assert comm.allreduce(n_cell_extr, op=MPI.SUM) == 18 - else: - with pytest.raises(ValueError): - extracted_tree = EP.extract_part_from_family(part_tree, 'EXTRACT', comm) diff --git a/maia/algo/part/test/test_extraction_utils.py b/maia/algo/part/test/test_extraction_utils.py deleted file mode 100644 index db9946e3..00000000 --- a/maia/algo/part/test/test_extraction_utils.py +++ /dev/null @@ -1,232 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns - -from maia.algo.part import extraction_utils as EU - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -dtype = 'I4' if pdm_gnum_dtype == np.int32 else 'I8' - -import Pypdm.Pypdm as PDM - -@pytest_parallel.mark.parallel(2) -def test_discover_containers(comm): - part_zone = [parse_yaml_cgns.to_node( - """ - VolZone.P0.N0 Zone_t: - FS1 FlowSolution_t: - GridLocation GridLocation_t "Vertex": - Field DataArray_t [9,3,2]: - ZSR1 ZoneSubRegion_t: - BCRegionName Descriptor_t "BC": - Field DataArray_t [9,3,2]: - ZoneBC ZoneBC_t: - BC BC_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[9,3,2]]: - ZoneGC ZoneGridConnectivity_t: - GC GridConnectivity_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[4,3,7]]: - """),parse_yaml_cgns.to_node( - """ - VolZone.P1.N0 Zone_t: - FS2 FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - ZSR2 ZoneSubRegion_t: - GridConnectivityRegionName Descriptor_t "GC": - Field DataArray_t [9,3,2]: - ZSR3 ZoneSubRegion_t: - GridLocation GridLocation_t "CellCenter" : - PointRange IndexRange_t [[4,4],[1,1],[8,8]]: - Field DataArray_t [9,3,2]: - ZoneGC ZoneGridConnectivity_t: - GC GridConnectivity_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[4,3,7]]: - """)][comm.Get_rank()] - mask_container, loc, partial_fld = EU.discover_containers([part_zone], 'FS1', 'PointList', 'IndexArray_t', comm) - assert loc=='Vertex' and not partial_fld - - mask_container, loc, partial_fld = EU.discover_containers([part_zone], 'FS2', 'PointList', 'IndexArray_t', comm) - assert mask_container is None and loc=='' and not partial_fld - - mask_container, loc, partial_fld = EU.discover_containers([part_zone], 'ZSR1', 'PointList', 'IndexArray_t', comm) - assert loc=='FaceCenter' and partial_fld - - mask_container, loc, partial_fld = EU.discover_containers([part_zone], 'ZSR2', 'PointList', 'IndexArray_t', comm) - assert loc=='FaceCenter' and partial_fld - - with pytest.raises(AssertionError): - mask_container, loc, partial_fld = EU.discover_containers([part_zone], 'ZSR2', 'PointRange', 'IndexRange_t', comm) - - with pytest.raises(ValueError): - mask_container, loc, partial_fld = EU.discover_containers([part_zone], 'ZSR3', 'PointList', 'IndexArray_t', comm) - - mask_container, loc, partial_fld = EU.discover_containers([part_zone], 'ZSR3', 'PointRange', 'IndexRange_t', comm) - assert loc=='CellCenter' and partial_fld - - with pytest.raises(ValueError): - mask_container, loc, partial_fld = EU.discover_containers([part_zone], 'ZSR4', 'PointList', 'IndexArray_t', comm) - -def test_get_relative_pl(): - part_zone = parse_yaml_cgns.to_node( - """ - VolZone.P0.N0 Zone_t: - ZSR1 ZoneSubRegion_t: - BCRegionName Descriptor_t "BC": - ZSR2 ZoneSubRegion_t: - GridConnectivityRegionName Descriptor_t "GC": - ZSR3 ZoneSubRegion_t: - GridLocation GridLocation_t "CellCenter" : - PointList IndexArray_t [[4,8,1]]: - ZoneBC ZoneBC_t: - BC BC_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[9,3,2]]: - ZoneGC ZoneGridConnectivity_t: - GC GridConnectivity_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [[4,3,7]]: - """) - zsr1 = PT.get_child_from_name(part_zone,'ZSR1') - zsr2 = PT.get_child_from_name(part_zone,'ZSR2') - zsr3 = PT.get_child_from_name(part_zone,'ZSR3') - pl1_n = PT.get_value(EU.get_relative_pl(zsr1, part_zone))[0] - pl2_n = PT.get_value(EU.get_relative_pl(zsr2, part_zone))[0] - pl3_n = PT.get_value(EU.get_relative_pl(zsr3, part_zone))[0] - assert np.array_equal(pl1_n, np.array([9,3,2],dtype=np.int32)) - assert np.array_equal(pl2_n, np.array([4,3,7],dtype=np.int32)) - assert np.array_equal(pl3_n, np.array([4,8,1],dtype=np.int32)) - - -def test_local_pl_offset(): - zone = PT.new_Zone('Zone', type='Unstructured') - PT.new_NGonElements('NGon', erange=[1, 10], parent=zone) - PT.new_NFaceElements('NFace', erange=[11, 20], parent=zone) - assert EU.local_pl_offset(zone, 0) == 0 - assert EU.local_pl_offset(zone, 2) == 0 - assert EU.local_pl_offset(zone, 3) == 10 - zone = PT.new_Zone('Zone', type='Unstructured') - PT.new_NFaceElements('NFace', erange=[1, 5], parent=zone) - PT.new_NGonElements('NGon', erange=[6, 10], parent=zone) - assert EU.local_pl_offset(zone, 2) == 5 - assert EU.local_pl_offset(zone, 3) == 0 - zone = PT.new_Zone('Zone', type='Unstructured') - PT.new_Elements('TRI_3', type='TRI_3',erange=[1, 5], parent=zone) - PT.new_Elements('BAR_2', type='BAR_2',erange=[6,15], parent=zone) - assert EU.local_pl_offset(zone, 0) == 0 - assert EU.local_pl_offset(zone, 1) == 5 - assert EU.local_pl_offset(zone, 2) == 0 - - -@pytest_parallel.mark.parallel(2) -def test_get_partial_container_stride_and_order(comm): - if comm.Get_rank()==0: - pt = """ - Zone.P0.N0 Zone_t: - NGonElements Elements_t I4 [22,0]: - ElementRange IndexRange_t I4 [1,5]: - FSol_A FlowSolution_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t I4 [[2,4,1]]: - - Zone.P0.N1 Zone_t: - NGonElements Elements_t I4 [22,0]: - ElementRange IndexRange_t I4 [1,3]: - FSol_A FlowSolution_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t I4 [[3]]: - """ - p1_lngn = [np.array([1,3,5,7,9], dtype=pdm_gnum_dtype)] - p2_lngn = [np.array([1,2,3,4,5], dtype=pdm_gnum_dtype), - np.array([6,7,8], dtype=pdm_gnum_dtype)] - p1_to_p2 = [np.array([9,10,9,10,13],dtype=pdm_gnum_dtype)] - p1_to_p2_idx = [np.array([0,1,2,3,4,5], dtype=np.int32)] - else: - pt = """ - Zone.P1.N0 Zone_t: - NGonElements Elements_t I4 [22,0]: - ElementRange IndexRange_t I4 [1,5]: - FSol_A FlowSolution_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t I4 [[4,2]]: - """ - p1_lngn = [np.array([2,4,6,8], dtype=pdm_gnum_dtype)] - p2_lngn = [np.array([9,10,11,12,13], dtype=pdm_gnum_dtype)] - p1_to_p2 = [np.array([1,2,6,7], dtype=pdm_gnum_dtype)] - p1_to_p2_idx = [np.array([0,1,2,3,4], dtype=np.int32)] - - ''' - part1 = isosurface or extract_part (only defined here by its lngn `p1_lngn` - and parent connectivity `p1_to_p2*`) - part2 = volumic parent (defined by the yaml tree `pt`) - - To get part_gnum1: start from ref_lnum2, extract gnum2 using p2_lngn; then search - the positions of these gnum in p1_to_p2. Finaly, read p1_lngn at these positions - - PROC 0: - part0: - part_gnum1_idx = [0 1 2] - part_gnum1 = [2 4] - ref_lnum2 = [1 2] - --> first two elts of part2 on proc0 - are linked with [2 4] of part1 - (proc1: p1_to_p2 = [1,2,x,x] and p1_lngn = [2,4,x,x]) - PL_part2 = [2 4 1], elts 2 and 1 are a parent of an elt of part1 - (because 2 and 1 are present in ref_lnum2) - --> pl_gnum1 = [2 0] to put data on same order than part1_gnum - (pl_part2[pl_gnum1]=[1 2]) - stride = [1 1] because elts [1 2] of part2 must send data - part1: - part_gnum1_idx = [0 1 2] - part_gnum1 = [6 8] - ref_lnum2 = [1 2] - --> first two elts of part2 on proc0 - are linked with [6 8] of part1 - (proc1: p1_to_p2 = [x,x,6,7] and p1_lngn = [x,x,6,8]) - PL_part2 = [] - (because no element of pl occurs in ref_lnum2) - --> pl_gnum1=[] - stride =[0 0] because no elts of part2 must send data - - PROC 1: - part0: - part_gnum1_idx = [0 2 4 5] - part_gnum1 = [1 5 3 7 9] - ref_lnum2 = [1 2 5] - --> elts [1 2 5] of part2 on proc1 - are linked with [1,5,3,7,9] of part1 - (proc0: p1_to_p2 = [9,10,9,10,13] - order is not the same because of - part_to_part) - PL_part2 = [4 2], elts 10 only is a parent of two elt of part1 (12 not appears) - (only 2 is present in ref_lnum2, at position 1) - --> pl_gnum1 = [1 1] to put data on same order than part1_gnum - and duplicate it (pl_part2[pl_gnum1]=[2 2] which reference to elt 10) - stride = [0 0 1 1 0] because 10 will send data to 2 elts of part1 - which are 3 and 7 - ''' - - - part_tree = parse_yaml_cgns.to_cgns_tree(pt) - part_zones = PT.get_all_Zone_t(part_tree) - - # > P2P Object - ptp = PDM.PartToPart(comm, p1_lngn, p2_lngn, p1_to_p2_idx, p1_to_p2) - - # Container - pl_gnum1, stride = EU.get_partial_container_stride_and_order(part_zones, 'FSol_A', 'Vertex', ptp, comm) - if comm.Get_rank()==0: - assert np.array_equal(pl_gnum1[0], np.array([2,0], dtype=pdm_gnum_dtype)) - assert np.array_equal(pl_gnum1[1], np.array([ ], dtype=pdm_gnum_dtype)) - assert np.array_equal( stride[0], np.array([1,1], dtype=pdm_gnum_dtype)) - assert np.array_equal( stride[1], np.array([0,0], dtype=pdm_gnum_dtype)) - if comm.Get_rank()==1: - assert np.array_equal(pl_gnum1[0], np.array([1,1], dtype=pdm_gnum_dtype)) - assert np.array_equal( stride[0], np.array([0,0,1,1,0], dtype=pdm_gnum_dtype)) - diff --git a/maia/algo/part/test/test_interpolate.py b/maia/algo/part/test/test_interpolate.py deleted file mode 100644 index 7daaf537..00000000 --- a/maia/algo/part/test/test_interpolate.py +++ /dev/null @@ -1,383 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory import dcube_generator as DCG - -from maia.algo.part import interpolate as ITP - -dtype = 'I4' if pdm_gnum_dtype == np.int32 else 'I8' - -src_part_0 = f""" -ZoneU Zone_t [[18,4,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0., 0.5, 1., 0., 0.5, 1., 0., 0.5, 1., 0., 0.5, 1., 0., 0.5, 1.]: - CoordinateY DataArray_t R8 [0., 0., 0., 0.5, 0.5, 0.5, 1., 1., 1., 0., 0., 0., 0.5, 0.5, 0.5, 1., 1., 1.]: - CoordinateZ DataArray_t R8 [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]: - NGon Elements_t [22,0]: - ElementRange IndexRange_t [1,20]: - ElementConnectivity DataArray_t: - I4 : [ 2, 5, 4, 1, 3, 6, 5, 2, 5, 8, 7, 4, 6, 9, 8, 5, 10, - 13, 14, 11, 11, 14, 15, 12, 13, 16, 17, 14, 14, 17, 18, 15, 1, 4, - 13, 10, 4, 7, 16, 13, 11, 14, 5, 2, 14, 17, 8, 5, 12, 15, 6, - 3, 15, 18, 9, 6, 10, 11, 2, 1, 11, 12, 3, 2, 4, 5, 14, 13, - 5, 6, 15, 14, 7, 8, 17, 16, 8, 9, 18, 17] - ElementStartOffset DataArray_t I4 [0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80]: - ParentElements DataArray_t: - I4 : [[1, 0], [2, 0], [4, 0], [3, 0], [1, 0], [2, 0], [4, 0], [3, 0], [1, 0], [4, 0], - [1, 2], [4, 3], [2, 0], [3, 0], [1, 0], [2, 0], [1, 4], [2, 3], [4, 0], [3, 0]] - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [1,2,3,4,5,6,7,8,13,14,17,18,21,22,25,27,29,31,33,35]: - NFaceElements Elements_t [23,0]: - ElementRange IndexRange_t [21,24]: - ElementConnectivity DataArray_t I4 [1,5,9,11,15,17,2,6,-11,13,16,18,4,8,-12,14,-18,20,3,7,10,12,-17,19]: - ElementStartOffset DataArray_t I4 [0,6,12,18,24]: - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [1,2,3,4]: - MySolution FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - val DataArray_t R8 [1.,2.,3.,4.]: - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t {dtype} [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]: - Cell DataArray_t {dtype} [1,2,3,4]: -""" -src_part_1 = f""" -ZoneU Zone_t [[18,4,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0., 0.5, 1., 0., 0.5, 1., 0., 0.5, 1., 0., 0.5, 1., 0., 0.5, 1.]: - CoordinateY DataArray_t R8 [0., 0., 0., 0.5, 0.5, 0.5, 1., 1., 1., 0., 0., 0., 0.5, 0.5, 0.5, 1., 1., 1.]: - CoordinateZ DataArray_t R8 [1., 1., 1., 1., 1., 1., 1., 1., 1., 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]: - NGon Elements_t [22,0]: - ElementRange IndexRange_t [1,20]: - ElementConnectivity DataArray_t: - I4 : [10, 11, 14, 13, 11, 12, 15, 14, 13, 14, 17, 16, 14, 15, 18, 17, 1, - 4, 5, 2, 2, 5, 6, 3, 4, 7, 8, 5, 5, 8, 9, 6, 10, 13, - 4, 1, 13, 16, 7, 4, 2, 5, 14, 11, 5, 8, 17, 14, 3, 6, 15, - 12, 6, 9, 18, 15, 1, 2, 11, 10, 2, 3, 12, 11, 13, 14, 5, 4, - 14, 15, 6, 5, 16, 17, 8, 7, 17, 18, 9, 8] - ElementStartOffset DataArray_t I4 [0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80]: - ParentElements DataArray_t: - I4 : [[1, 0], [2, 0], [3, 0], [4, 0], [1, 0], [2, 0], [3, 0], [4, 0], [1, 0], [3, 0], - [1, 2], [3, 4], [2, 0], [4, 0], [1, 0], [2, 0], [1, 3], [2, 4], [3, 0], [4, 0]] - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [5,6,7,8,9,10,11,12,15,16,19,20,23,24,26,28,30,32,34,36]: - NFaceElements Elements_t [23,0]: - ElementRange IndexRange_t [21,24]: - ElementConnectivity DataArray_t I4 [1,5,9,11,15,17,2,6,-11,13,16,18,3,7,10,12,-17,19,4,8,-12,14,-18,20]: - ElementStartOffset DataArray_t I4 [0,6,12,18,24]: - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [5,6,7,8]: - MySolution FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - val DataArray_t R8 [5.,6.,7.,8.]: - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t {dtype} [19,20,21,22,23,24,25,26,27,10,11,12,13,14,15,16,17,18]: - Cell DataArray_t {dtype} [5,6,7,8]: -""" -tgt_part_0 = f""" -ZoneU Zone_t [[12,2,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0.2, 0.2, 0.2, 0.7, 0.2, 0.7, 0.7, 0.2, 0.7, 0.7, 0.2, 0.7]: - CoordinateY DataArray_t R8 [0.2 , 0.2 , 0.2 , 0.2 , 0.7, 0.7, 0.2 , 0.7, 0.7, 0.2 , 0.7, 0.7]: - CoordinateZ DataArray_t R8 [-0.2 , 0.3, 0.8 , -0.2 , -0.2 , -0.2 , 0.3, 0.3, 0.3, 0.8, 0.8, 0.8]: - NGon Elements_t [22,0]: - ElementRange IndexRange_t [1,11]: - ElementConnectivity DataArray_t: - I4 : [4, 6, 5, 1, 2, 8, 9, 7, 3, 11, 12, 10, 1, 5, 8, 2, 2, - 8, 11, 3, 7, 9, 6, 4, 10, 12, 9, 7, 2, 7, 4, 1, 3, 10, - 7, 2, 5, 6, 9, 8, 8, 9, 12, 11] - ElementStartOffset DataArray_t I4 [0,4,8,12,16,20,24,28,32,36,40,44]: - ParentElements DataArray_t: - I4 : [[1, 0], [1, 2], [2, 0], [1, 0], [2, 0], [1, 0], [2, 0], [1, 0], [2, 0], [1, 0], [2,0]] - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [1,5,9,13,15,17,19,25,26,29,30]: - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t {dtype} [1,10,19,2,4,5,11,13,14,20,22,23]: - Cell DataArray_t {dtype} [1,5]: -""" -tgt_part_1 = f""" -ZoneU Zone_t [[16,3,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0.2, 0.2, 0.2, 0.7, 1.2, 0.2, 0.7, 0.7, 0.2, 0.7, 1.2, 0.7, 1.2, 0.2, 0.7, 1.2]: - CoordinateY DataArray_t R8 [1.2, 1.2, 1.2, 1.2, 1.2, 0.7, 0.7, 1.2, 0.7, 0.7, 0.7, 1.2, 1.2, 0.7, 0.7, 0.7]: - CoordinateZ DataArray_t R8 [-.2, 0.3, 0.8, 0.8, 0.8, -.2,-.2 ,-.2 , 0.3, 0.3, 0.3, 0.3, 0.3, 0.8, 0.8, 0.8]: - NGon Elements_t [22,0]: - ElementRange IndexRange_t [1,16]: - ElementConnectivity DataArray_t: - I4 : [7, 8, 1, 6, 9, 2, 12, 10, 10, 11, 13, 12, 14, 3, 4, 15, 15, - 4, 5, 16, 6, 1, 2, 9, 9, 2, 3, 14, 10, 12, 8, 7, 15, 4, - 12, 10, 16, 5, 13, 11, 6, 9, 10, 7, 9, 14, 15, 10, 10, 15, 16, - 11, 1, 8, 12, 2, 2, 12, 4, 3, 12, 13, 5, 4] - ElementStartOffset DataArray_t I4 [0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64]: - ParentElements DataArray_t: - I4 : [[3, 0], [3, 1], [2, 0], [1, 0], [2, 0], [3, 0], [1, 0], [3, 0], - [1, 2], [2, 0], [3, 0], [1, 0], [2, 0], [3, 0], [1, 0], [2, 0]] - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [3,7,8,11,12,14,16,18,20,24,29,30,32,33,34,36]: - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t {dtype} [7,16,25,26,27,4,5,8,13,14,15,17,18,22,23,24]: - Cell DataArray_t {dtype} [7,8,3]: -""" -tgt_part_2 = f""" -ZoneU Zone_t [[16,3,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [1.2, 1.2, 1.2, 1.2, 1.2, 0.7, 0.7, 0.7, 0.7, 0.7, 1.2, 0.7, 1.2, 0.7, 0.7, 1.2]: - CoordinateY DataArray_t R8 [0.2, 0.7, 1.2, 0.2, 0.2, 0.2, 0.7, 1.2, 0.2, 0.7, 0.7, 1.2, 1.2, 0.2, 0.7, 0.7]: - CoordinateZ DataArray_t R8 [-.2, -.2, -.2, 0.3, 0.8, -.2, -.2, -.2, 0.3, 0.3, 0.3, 0.3, 0.3, 0.8, 0.8, 0.8]: - NGon Elements_t [22,0]: - ElementRange IndexRange_t [1,16]: - ElementConnectivity DataArray_t: - I4 : [ 1, 2, 7, 6, 2, 3, 8, 7, 9, 10, 11, 4, 10, 12, 13, 11, 14, - 15, 16, 5, 9, 6, 7, 10, 10, 7, 8, 12, 14, 9, 10, 15, 4, 11, - 2, 1, 11, 13, 3, 2, 5, 16, 11, 4, 9, 4, 1, 6, 14, 5, 4, - 9, 7, 2, 11, 10, 10, 11, 16, 15, 8, 3, 13, 12] - ElementStartOffset DataArray_t I4 [0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64]: - ParentElements DataArray_t: - I4 : [[1, 0], [3, 0], [1, 2], [3, 0], [2, 0], [1, 0], [3, 0], [2, 0], - [1, 0], [3, 0], [2, 0], [1, 0], [2, 0], [1, 3], [2, 0], [3, 0]] - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [2,4,6,8,10,17,18,19,21,22,23,27,28,31,32,35]: - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t {dtype} [3,6,9,12,21,2,5,8,11,14,15,17,18,20,23,24]: - Cell DataArray_t {dtype} [2,6,4]: -""" - -@pytest_parallel.mark.parallel(2) -def test_create_src_to_tgt(comm): - #Here we just check if src_to_tgt is created - if comm.Get_rank() == 0: - pt = src_part_0 - else: - pt = src_part_1 - zones = parse_yaml_cgns.to_nodes(pt) - for zone in zones: - pe = PT.get_node_from_name(zone, 'ParentElements') - #Put it in F order - newpe = np.empty(pe[1].shape, dtype=np.int32, order='F') - newpe[:] = np.copy(pe[1][:]) - PT.set_value(pe, newpe) - - src_parts_per_dom = [zones] - tgt_parts_per_dom = [[PT.deep_copy(zone) for zone in zones]] - excp_target = np.array([1,2,3,4]) if comm.Get_rank() == 0 else np.array([5,6,7,8]) - src_to_tgt = ITP.create_src_to_tgt(src_parts_per_dom, tgt_parts_per_dom, comm) - assert (src_to_tgt[0]['target_idx'] == [0,1,2,3,4]).all() - assert (src_to_tgt[0]['target'] == excp_target).all() - src_to_tgt = ITP.create_src_to_tgt(src_parts_per_dom, tgt_parts_per_dom, comm, strategy='Closest') - assert (src_to_tgt[0]['target_idx'] == [0,1,2,3,4]).all() - assert (src_to_tgt[0]['target'] == excp_target).all() - - for tgt_zones in tgt_parts_per_dom: - for tgt_zone in tgt_zones: - cx = PT.get_node_from_name(zone, 'CoordinateX') - cx[1] += .5 - excp_target = np.array([2,1,3,4]) if comm.Get_rank() == 0 else np.array([6,5,8,7]) - src_to_tgt = ITP.create_src_to_tgt(src_parts_per_dom, tgt_parts_per_dom, comm, strategy='LocationAndClosest') - assert (src_to_tgt[0]['target'] == excp_target).all() - - excp_target = np.array([2,3]) if comm.Get_rank() == 0 else np.array([6,8]) - src_to_tgt = ITP.create_src_to_tgt(src_parts_per_dom, tgt_parts_per_dom, comm, strategy='Location') - assert (src_to_tgt[0]['target'] == excp_target).all() - -def test_interpolator_reductions(): - class Empty: #Used to create a interpolator like object - pass - fake_interpolator = Empty() - - fake_interpolator.sending_gnums = [{'come_from_idx' : np.array([0,1,2,3])}] - data = np.array([1,2,3], np.int32) - out = ITP.Interpolator._reduce_single_val(fake_interpolator, 0, data) - assert out is data - - fake_interpolator.sending_gnums = [{'come_from_idx' : np.array([0,2,4,6])}] - fake_interpolator.tgt_dist = [np.array([1,1,0,1,3,1])] - data = np.array([1,2, 10,11, 20,30], np.float64) - out = ITP.Interpolator._reduce_mean_dist(fake_interpolator, 0, data) - assert (out == np.array([1.5, 10., 27.5])).all() - - fake_interpolator.sending_gnums = [{'come_from_idx' : np.array([0,2,5,6])}] - fake_interpolator.tgt_dist = [np.array([1,1, 0,1,3, 1])] - data = np.array([1,2, 10,11,20, 30], np.float64) - out = ITP.Interpolator._reduce_mean_dist(fake_interpolator, 0, data) - assert (out == np.array([1.5, 10., 30.0])).all() - -@pytest_parallel.mark.parallel(2) -def test_interpolate_fields(comm): - if comm.Get_rank() == 0: - pt = src_part_0 - expected_sol = np.array([2.,2.,2.,3.,3.,3.,3.,3.,3., 2.,2.,2.,3.,3.,3.,3.,3.,3.]) - else: - pt = src_part_1 - expected_sol = np.array([6.,6.,6.,8.,8.,8.,8.,8.,8., 2.,2.,2.,3.,3.,3.,3.,3.,3.]) - part_tree = parse_yaml_cgns.to_cgns_tree(pt) - - src_parts_per_dom = [PT.get_all_Zone_t(part_tree)] - tgt_parts_per_dom = [[PT.deep_copy(zone) for zone in PT.get_all_Zone_t(part_tree)]] - for tgt_zones in tgt_parts_per_dom: - for tgt_zone in tgt_zones: - cx = PT.get_node_from_name(tgt_zone, 'CoordinateX') - cy = PT.get_node_from_name(tgt_zone, 'CoordinateY') - cz = PT.get_node_from_name(tgt_zone, 'CoordinateZ') - cx[1] += .55 - cy[1] += .05 - cz[1] -= .05 - - src_to_tgt = ITP.create_src_to_tgt(src_parts_per_dom, tgt_parts_per_dom, comm, 'CellCenter', 'Vertex') - interpolator = ITP.Interpolator(src_parts_per_dom, tgt_parts_per_dom, src_to_tgt, 'CellCenter', 'Vertex', comm) - interpolator.exchange_fields('MySolution') - - for tgt_zones in tgt_parts_per_dom: - for tgt_zone in tgt_zones: - fs = PT.get_node_from_name(tgt_zone, 'MySolution') - assert PT.Subset.GridLocation(fs) == 'Vertex' - assert (PT.get_child_from_name(fs, 'val')[1] == expected_sol).all() - -@pytest_parallel.mark.parallel(2) -class Test_interpolation_api(): - src_zone_0 = parse_yaml_cgns.to_node(src_part_0) - src_zone_1 = parse_yaml_cgns.to_node(src_part_1) - tgt_zone_0 = parse_yaml_cgns.to_node(tgt_part_0) - tgt_zone_1 = parse_yaml_cgns.to_node(tgt_part_1) - tgt_zone_2 = parse_yaml_cgns.to_node(tgt_part_2) - # - For 1 to 9 (bottom) : 1 2 2 4 3 3 4 3 3 - # - For 10 to 18 (middle) : 1 2 2 4 3 3 4 3 3 - # - For 19 to 27 (top) : 5 6 6 7 8 8 7 8 8 - expected_vtx_sol = [ np.array([1., 1, 5, 2, 4, 3, 2, 4, 3, 6, 7, 8]), - np.array([4., 4, 7, 8, 8, 4, 3, 3, 4, 3, 3, 3, 3, 7, 8, 8]), - np.array([2., 3, 3, 2, 6, 2, 3, 3, 2, 3, 3, 3, 3, 6, 8, 8])] - expected_cell_sol = [ np.array([1., 5.]), - np.array([7., 8., 4.]), - np.array([2., 6., 3.])] - all_zones = [src_zone_0, src_zone_1, tgt_zone_0, tgt_zone_1, tgt_zone_2] - - def test_interpolate_from_parts_per_dom(self,comm): - if comm.Get_rank() == 0: - src_parts_per_dom = [[self.src_zone_0]] - tgt_parts_per_dom = [[self.tgt_zone_0, self.tgt_zone_1]] - expected_vtx_sol = [self.expected_vtx_sol[k] for k in [0,1]] - elif comm.Get_rank() == 1: - src_parts_per_dom = [[self.src_zone_1]] - tgt_parts_per_dom = [[self.tgt_zone_2]] - expected_vtx_sol = [self.expected_vtx_sol[k] for k in [2]] - - ITP.interpolate_from_parts_per_dom(src_parts_per_dom, tgt_parts_per_dom, comm, \ - ['MySolution'], 'Vertex', strategy='Closest') - - for tgt_zones in tgt_parts_per_dom: - for i_tgt, tgt_zone in enumerate(tgt_zones): - fs = PT.get_child_from_name(tgt_zone, 'MySolution') - assert PT.Subset.GridLocation(fs) == 'Vertex' - assert (PT.get_child_from_name(fs, 'val')[1] == expected_vtx_sol[i_tgt]).all() - - def test_interpolate_from_dom_part_trees(self,comm): - src_tree = PT.new_CGNSTree() - src_base = PT.new_CGNSBase(parent=src_tree) - tgt_tree = PT.new_CGNSTree() - tgt_base = PT.new_CGNSBase(parent=tgt_tree) - - if comm.Get_rank() == 0: - self.src_zone_0[0] = 'Source.P0.N0' - self.tgt_zone_0[0] = 'Target.P0.N0' - self.tgt_zone_1[0] = 'Target.P0.N1' - self.tgt_zone_2[0] = 'Target.P0.N2' - PT.add_child(src_base, self.src_zone_0) - PT.add_child(tgt_base, self.tgt_zone_0) - PT.add_child(tgt_base, self.tgt_zone_1) - PT.add_child(tgt_base, self.tgt_zone_2) - expected_vtx_sol = [self.expected_vtx_sol[k] for k in [0,1,2]] - elif comm.Get_rank() == 1: - self.src_zone_1[0] = 'Source.P1.N0' - PT.add_child(src_base, self.src_zone_1) - expected_vtx_sol = [self.expected_vtx_sol[k] for k in []] - - ITP.interpolate_from_part_trees(src_tree, tgt_tree, comm, \ - ['MySolution'], 'Vertex', strategy='Closest') - - for i_tgt, tgt_zone in enumerate(PT.get_all_Zone_t(tgt_tree)): - fs = PT.get_child_from_name(tgt_zone, 'MySolution') - assert PT.Subset.GridLocation(fs) == 'Vertex' - assert (PT.get_child_from_name(fs, 'val')[1] == expected_vtx_sol[i_tgt]).all() - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("strategy", ['Closest', 'LocationAndClosest']) -def test_interpolation_mdom(strategy, comm): - # Source tree : 2 blocks 3**3 - dtree_src = PT.new_CGNSTree() - dbase_src = PT.new_CGNSBase(parent=dtree_src) - zoneA = PT.get_node_from_label(DCG.dcube_generate(4, 1.5, [0.,0.,0.], comm), 'Zone_t') - zoneB = PT.get_node_from_label(DCG.dcube_generate(4, 1.5, [1.5,0.,0.], comm), 'Zone_t') - PT.set_name(zoneA, 'SRCA') - PT.set_name(zoneB, 'SRCB') - PT.set_children(dbase_src, [zoneA, zoneB]) - # Target tree : 2 blocks 2**3 - dtree_tgt = PT.new_CGNSTree() - dbase_tgt = PT.new_CGNSBase(parent=dtree_tgt) - zoneA = PT.get_node_from_label(DCG.dcube_generate(3, 1., [0.,0.,-0.6], comm), 'Zone_t') - zoneB = PT.get_node_from_label(DCG.dcube_generate(3, 1., [1.,0.,1.1], comm), 'Zone_t') - PT.set_name(zoneA, 'TGTA') - PT.set_name(zoneB, 'TGTB') - PT.set_children(dbase_tgt, [zoneA, zoneB]) - #maia.io.dist_tree_to_file(dtree_src, 'source.hdf', comm) - #maia.io.dist_tree_to_file(dtree_tgt, 'target.hdf', comm) - - if comm.Get_rank() == 0: - z_to_p = {'Base/TGTA' : [ ], 'Base/TGTB' : [.5]} - if comm.Get_rank() == 1: - z_to_p = {'Base/TGTA' : [1], 'Base/TGTB' : [.5]} - src_tree = maia.factory.partition_dist_tree(dtree_src, comm) - tgt_tree = maia.factory.partition_dist_tree(dtree_tgt, comm, zone_to_parts=z_to_p) - - interpolator = ITP.create_interpolator_from_part_trees(src_tree, tgt_tree, comm, \ - 'CellCenter', 'CellCenter', strategy=strategy) - - # Add sol to exchange - for zone in PT.get_all_Zone_t(src_tree): - dom_flag = {'A': 1, 'B':2}[PT.get_name(zone)[3]] - fields = {'gnum' : MT.getGlobalNumbering(zone, 'Cell')[1], - 'dom' : dom_flag*np.ones(PT.Zone.n_cell(zone), np.int32)} - PT.new_FlowSolution('FlowSol', loc='CellCenter', fields=fields, parent=zone) - interpolator.exchange_fields('FlowSol') - - maia.transfer.part_tree_to_dist_tree_all(dtree_tgt, tgt_tree, comm) - assert (PT.get_node_from_path(dtree_tgt, 'Base/TGTA/FlowSol/dom')[1] == 1).all() - assert (PT.get_node_from_path(dtree_tgt, 'Base/TGTB/FlowSol/dom')[1] == [1,2,1,2]).all() - # Careful, expected gnum depends on how the mesh is split. Today same value for two ranks - assert (PT.get_node_from_path(dtree_tgt, 'Base/TGTA/FlowSol/gnum')[1] == [1,2,4,5]).all() - assert (PT.get_node_from_path(dtree_tgt, 'Base/TGTB/FlowSol/gnum')[1] == [21,19,24,22]).all() - # maia.algo.pe_to_nface(dtree_tgt, comm) - # maia.io.dist_tree_to_file(dtree_tgt, 'dtgt_with_sol.cgns', comm) - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("out_loc", ['Vertex', 'CellCenter']) -def test_interpolation_vertex_src(comm, out_loc): - src_tree = maia.factory.generate_dist_block(11, 'S', comm) - tgt_tree = maia.factory.generate_dist_block(5, 'S', comm) - - psrc_tree = maia.factory.partition_dist_tree(src_tree, comm) - ptgt_tree = maia.factory.partition_dist_tree(tgt_tree, comm) - - for zone in PT.iter_all_Zone_t(psrc_tree): - cx,cy,cz = PT.Zone.coordinates(zone) - - PT.new_FlowSolution('FS', loc='Vertex', fields={'cx':cx, 'cy':cy, 'cz':cz}, parent=zone) - - maia.algo.part.interpolate_from_part_trees(psrc_tree, ptgt_tree, comm, ['FS'], out_loc) - - tgt_fs = PT.get_node_from_name(ptgt_tree, 'FS') - assert tgt_fs is not None and PT.Subset.GridLocation(tgt_fs) == out_loc - - with pytest.raises(NotImplementedError): - maia.algo.part.interpolate_from_part_trees(psrc_tree, ptgt_tree, comm, ['FS'], out_loc, strategy='Location') \ No newline at end of file diff --git a/maia/algo/part/test/test_isosurf.py b/maia/algo/part/test/test_isosurf.py deleted file mode 100644 index ed378035..00000000 --- a/maia/algo/part/test/test_isosurf.py +++ /dev/null @@ -1,163 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns - -from maia.algo.part import isosurf as ISO - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -dtype = 'I4' if pdm_gnum_dtype == np.int32 else 'I8' - - -def test_copy_referenced_families(): - source_base = parse_yaml_cgns.to_node( - """ - Base CGNSBase_t: - Toto Family_t: - Tata Family_t: - Titi Family_t: - """) - target_base = parse_yaml_cgns.to_node( - """ - Base CGNSBase_t: - Tyty Family_t: #Already in target tree - ZoneA Zone_t: - FamilyName FamilyName_t "Toto": - AddFamilyName AdditionalFamilyName_t "Tutu": #Not in source tree - ZoneB Zone_t: - AdditionalFamilyName AdditionalFamilyName_t "Titi": - """) - ISO.copy_referenced_families(source_base, target_base) - assert PT.get_child_from_name(target_base, 'Tyty') is not None - assert PT.get_child_from_name(target_base, 'Toto') is not None - assert PT.get_child_from_name(target_base, 'Titi') is not None - assert PT.get_child_from_name(target_base, 'Tata') is None - - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("from_api", [False, True]) -def test_exchange_field_one_domain(from_api, comm): - if comm.Get_rank() == 0: - yt_vol = f""" - VolZone.P0.N0 Zone_t: - NGonElements Elements_t [22,0]: - ElementRange IndexRange_t [1,8]: - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [1,3,5,7]: - ZoneBC ZoneBC_t: - Zmin BC_t: - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t {dtype} [[1,2,3,4]]: - FSolVtx FlowSolution_t: - GridLocation GridLocation_t "Vertex": - fieldC DataArray_t [60., 40, 20, 50, 30, 10]: - FSolCell FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - fieldA DataArray_t [40., 30., 20., 10.]: - fieldB DataArray_t [400., 300., 200., 100.]: - FSolBC ZoneSubRegion_t: - BCRegionName Descriptor_t "Zmin": - fieldD DataArray_t R8 [-1., -3., -5., -7.]: - :CGNS#GlobalNumbering UserDefinedData_t: - Cell DataArray_t {dtype} [4,3,2,1]: - Vertex DataArray_t {dtype} [6,4,2,5,3,1]: - """ - yt_surf = f""" - VolZone.P0.N0 Zone_t: - BAR_2 Elements_t [3,0]: - ElementRange IndexRange_t [1,3]: - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [3,2]: - :CGNS#GlobalNumbering UserDefinedData_t: - Cell DataArray_t {dtype} [2]: - Vertex DataArray_t {dtype} [1,2]: - maia#surface_data UserDefinedData_t: - Vtx_parent_weight DataArray_t [1., 1.]: - Vtx_parent_gnum DataArray_t {dtype} [6,5]: - Vtx_parent_idx DataArray_t I4 [0,1,2]: - Cell_parent_gnum DataArray_t {dtype} [4]: - Face_parent_bnd_edges DataArray_t {dtype} [5, 1]: - """ - else: - yt_surf = f""" - VolZone.P1.N0 Zone_t: - BAR_2 Elements_t [3,0]: - ElementRange IndexRange_t [1,3]: - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [1]: - :CGNS#GlobalNumbering UserDefinedData_t: - Cell DataArray_t {dtype} [1,3]: - Vertex DataArray_t {dtype} [2,3]: - maia#surface_data UserDefinedData_t: - Vtx_parent_weight DataArray_t [1., .5, .5]: - Vtx_parent_gnum DataArray_t {dtype} [5,1,2]: - Vtx_parent_idx DataArray_t I4 [0,1,3]: - Cell_parent_gnum DataArray_t {dtype} [3, 1]: - Face_parent_bnd_edges DataArray_t {dtype} [3]: - """ - yt_vol = f""" - VolZone.P1.N0 Zone_t: - NGonElements Elements_t [22,0]: - ElementRange IndexRange_t [1,8]: - :CGNS#GlobalNumbering UserDefinedData_t: - Element DataArray_t {dtype} [2,4,6,8]: - FSolVtx FlowSolution_t: - GridLocation GridLocation_t "Vertex": - fieldC DataArray_t [70., 80]: - FSolCell FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - fieldA DataArray_t [50.]: - fieldB DataArray_t [500.]: - :CGNS#GlobalNumbering UserDefinedData_t: - Cell DataArray_t {dtype} [5]: - Vertex DataArray_t {dtype} [7,8]: - """ - - if comm.Get_rank() == 0: - expected_A = np.array([40.]) - expected_B = np.array([400.]) - expected_C = np.array([60., 50.]) - expected_D = np.array([-5., -1.]) - else: - expected_A = np.array([30., 10.]) - expected_B = np.array([300., 100.]) - expected_C = np.array([50., 15.]) - expected_D = np.array([-3.]) - - if from_api: - iso_tree = parse_yaml_cgns.to_cgns_tree(yt_surf) - vol_tree = parse_yaml_cgns.to_cgns_tree(yt_vol) - ISO._exchange_field(vol_tree, iso_tree, ["FSolCell", "FSolVtx", "FSolBC"], comm) - iso_zone = PT.get_all_Zone_t(iso_tree)[0] - else: - iso_zone = parse_yaml_cgns.to_node(yt_surf) - vol_zones = parse_yaml_cgns.to_nodes(yt_vol) - ISO.exchange_field_one_domain(vol_zones, iso_zone, ["FSolCell", "FSolVtx", "FSolBC"], comm) - - assert PT.Subset.GridLocation(PT.get_node_from_name(iso_zone, "FSolCell")) == "CellCenter" - assert PT.Subset.GridLocation(PT.get_node_from_name(iso_zone, "FSolVtx")) == "Vertex" - assert np.array_equal(PT.get_node_from_path(iso_zone, "FSolCell/fieldA")[1], expected_A) - assert np.array_equal(PT.get_node_from_path(iso_zone, "FSolCell/fieldB")[1], expected_B) - assert np.array_equal(PT.get_node_from_path(iso_zone, "FSolVtx/fieldC")[1], expected_C) - assert np.array_equal(PT.get_node_from_path(iso_zone, "FSolBC/fieldD")[1], expected_D) - - -@pytest.mark.skipif(not maia.pdma_enabled, reason="Require ParaDiGMA") -@pytest_parallel.mark.parallel(2) -def test_isosurf_one_domain(comm): - dist_tree = maia.factory.generate_dist_block(3, "Poly", comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - part_zones = PT.get_all_Zone_t(part_tree) - iso_zone = ISO.iso_surface_one_domain(part_zones, "PLANE", [1,0,0,0.25], "TRI_3", "hilbert", comm) - - assert PT.Zone.n_cell(iso_zone) == 16 and PT.Zone.n_vtx(iso_zone) == 15 - assert (PT.get_node_from_name(iso_zone, 'CoordinateX')[1] == 0.25).all() - assert (PT.get_child_from_predicates(iso_zone, 'TRI_3/ElementRange')[1] == np.array([ 1, 16], dtype=np.int32)).all() - assert (PT.get_child_from_predicates(iso_zone, 'BAR_2/ElementRange')[1] == np.array([17, 24], dtype=np.int32)).all() - - assert PT.get_label(PT.get_child_from_name(iso_zone, "maia#surface_data")) == 'UserDefinedData_t' - diff --git a/maia/algo/part/test/test_localize.py b/maia/algo/part/test/test_localize.py deleted file mode 100644 index 23173d8c..00000000 --- a/maia/algo/part/test/test_localize.py +++ /dev/null @@ -1,121 +0,0 @@ -import pytest -import pytest_parallel -import os -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.factory import dcube_generator as DCG -from maia.factory import partition_dist_tree - -from maia.utils import test_utils as TU -from maia.algo.part import localize as LOC - -@pytest_parallel.mark.parallel(1) -def test_get_part_data(comm): - dtree = DCG.dcube_generate(3, 1., [0.,0.,0.], comm) - tree = partition_dist_tree(dtree, comm) - zone = PT.get_all_Zone_t(tree)[0] - data = LOC._get_part_data(zone) - assert len(data) == 8 - assert (data[2] == MT.getGlobalNumbering(zone, 'Cell')[1]).all() - assert data[6].size == 3*PT.Zone.n_vtx(zone) #Coords - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("reverse", [False, True]) -def test_mesh_location(reverse, comm): - dtree = DCG.dcube_generate(3, 1., [0.,0.,0.], comm) - if comm.Get_rank() == 0: - zone_to_parts = {'Base/zone' : [.5]} - tgt_clouds = [(np.array([.6,.9,0]), np.array([2], pdm_gnum_dtype)), (np.array([1.6,.9,0]), np.array([4], pdm_gnum_dtype))] - else: - zone_to_parts = {'Base/zone' : [.25, .25]} - tgt_clouds = [(np.array([.6,.9,0, .6,.9,.8, 1.2, 0.1, 0.1]), np.array([1,3,5], pdm_gnum_dtype))] - tree = partition_dist_tree(dtree, comm, zone_to_parts=zone_to_parts) - src_parts = [LOC._get_part_data(zone) for zone in PT.get_all_Zone_t(tree)] - - if reverse: - tgt_data, src_data = LOC._mesh_location(src_parts, [], comm, reverse) - assert all([data['elt_pts_inside_idx'].sum() == 0 for data in src_data]) - assert all([data['elt_pts_inside_idx'].size-1 == PT.Zone.n_cell(part) for data,part in zip(src_data, PT.get_all_Zone_t(tree))]) - assert all([data['points_gnum'].size == 0 for data in src_data]) - else: - tgt_data = LOC._mesh_location(src_parts, [], comm, reverse) - assert tgt_data == [] - - if reverse: - tgt_data, src_data = LOC._mesh_location(src_parts, tgt_clouds, comm, reverse) - else: - tgt_data = LOC._mesh_location(src_parts, tgt_clouds, comm, reverse) - - if comm.rank == 0: - expected_tgt_data = [{'located_ids' : [0], 'unlocated_ids' : [], 'location' : [4]}, - {'located_ids' : [], 'unlocated_ids' : [0], 'location' : []}] - expected_src_data = [{'elt_pts_inside_idx' : [0,0,0,0,1], 'points_gnum' : [3]}] - if comm.rank == 1: - expected_tgt_data = [{'located_ids' : [0,1], 'unlocated_ids' : [2], 'location' : [4,8]}] - expected_src_data = [{'points_gnum' : []}, - {'elt_pts_inside_idx' : [0,0,2], 'points_gnum' : [1,2]}] - - for i_part, expct_data in enumerate(expected_tgt_data): - for key in expct_data: - assert (tgt_data[i_part][key] == expct_data[key]).all() - if reverse: - for i_part, expct_data in enumerate(expected_src_data): - for key in expct_data: - assert np.allclose(src_data[i_part][key], expct_data[key]) - -@pytest_parallel.mark.parallel(1) -def test_mesh_location_mdom(comm): - yaml_path = os.path.join(TU.mesh_dir, 'S_twoblocks.yaml') - dtree_src = DCG.dcube_generate(11, 20., [0.,0.,0.], comm) - dtree_tgt = maia.io.file_to_dist_tree(yaml_path, comm) - - tree_src = partition_dist_tree(dtree_src, comm) - tree_tgt = partition_dist_tree(dtree_tgt, comm) - - tgt_parts_per_dom = [PT.get_nodes_from_name_and_label(tree_tgt, 'Large*', 'Zone_t'), - PT.get_nodes_from_name_and_label(tree_tgt, 'Small*', 'Zone_t')] - src_parts_per_dom = [PT.get_all_Zone_t(tree_src)] - - result, result_inv = LOC._localize_points( - src_parts_per_dom, tgt_parts_per_dom, 'CellCenter', comm, reverse=True) - # We should get all the cells of Large + 4*4*6 cells of Small - _result_inv = result_inv[0][0] - dom1_idx = np.where(_result_inv['domain'] == 1)[0] - dom2_idx = np.where(_result_inv['domain'] == 2)[0] - assert dom1_idx.size == PT.Zone.n_cell(tgt_parts_per_dom[0][0]) - assert dom2_idx.size == 4*4*6 - # Gnum should have been reshifted - assert _result_inv['points_gnum'][dom1_idx].max() <= PT.Zone.n_cell(tgt_parts_per_dom[0][0]) - assert _result_inv['points_gnum'][dom2_idx].max() <= PT.Zone.n_cell(tgt_parts_per_dom[1][0]) - - -@pytest_parallel.mark.parallel(3) -def test_localize_points(comm): - dtree_src = DCG.dcube_generate(5, 1., [0.,0.,0.], comm) - dtree_tgt = DCG.dcube_generate(4, 1., [.4,.05,.05], comm) - tree_src = partition_dist_tree(dtree_src, comm) - tree_tgt = partition_dist_tree(dtree_tgt, comm) - - tree_src_back = PT.deep_copy(tree_src) - LOC.localize_points(tree_src, tree_tgt, 'CellCenter', comm) - assert PT.is_same_tree(tree_src_back, tree_src) - tgt_zone = PT.get_all_Zone_t(tree_tgt)[0] - loc_node = PT.get_node_from_name_and_label(tgt_zone, 'Localization', 'DiscreteData_t') - assert loc_node is not None and PT.Subset.GridLocation(loc_node) == 'CellCenter' - assert PT.get_value(PT.get_child_from_name(loc_node, 'DomainList')) == "Base/zone" - - # Check result on dist tree to not rely on partitioning - maia.transfer.part_tree_to_dist_tree_all(dtree_tgt, tree_tgt, comm) - if comm.rank == 0: - expected_dsrc_id = np.array([3,4,-1,11,12,-1,15,16,-1]) - elif comm.rank == 1: - expected_dsrc_id = np.array([35,36,-1,43,44,-1,47,48,-1]) - elif comm.rank == 2: - expected_dsrc_id = np.array([51,52,-1,59,60,-1,63,64,-1]) - - assert (PT.get_node_from_name(dtree_tgt, 'SrcId')[1] == expected_dsrc_id).all() diff --git a/maia/algo/part/test/test_move_loc.py b/maia/algo/part/test/test_move_loc.py deleted file mode 100644 index db52e2e4..00000000 --- a/maia/algo/part/test/test_move_loc.py +++ /dev/null @@ -1,112 +0,0 @@ -import os -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT - -import maia -from maia.utils import test_utils as TU - -import maia.algo.part.move_loc as ML - -@pytest_parallel.mark.parallel([1,2]) -@pytest.mark.parametrize("cross_domain", [False, True]) -def test_centers_to_nodes(cross_domain, comm): - yaml_path = os.path.join(TU.sample_mesh_dir, 'quarter_crown_square_8.yaml') - dist_tree = maia.io.file_to_dist_tree(yaml_path, comm) - for label in ['FlowSolution_t', 'DiscreteData_t', 'ZoneSubRegion_t']: - PT.rm_nodes_from_label(dist_tree, label) # Cleanup - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - # Create sol on partitions - for part in PT.get_all_Zone_t(part_tree): - gnum = PT.maia.getGlobalNumbering(part, 'Cell')[1] - PT.new_FlowSolution('FSol', loc='CellCenter', fields={'gnum': gnum}, parent=part) - - ML.centers_to_nodes(part_tree, comm, ['FSol'], idw_power=0, cross_domain=cross_domain) - - maia.transfer.part_tree_to_dist_tree_only_labels(dist_tree, part_tree, ['FlowSolution_t'], comm) - dsol_vtx = PT.get_node_from_name(dist_tree, 'FSol#Vtx') - dfield_vtx = PT.get_node_from_name(dsol_vtx, 'gnum')[1] - - if cross_domain: - expected_dfield_f = np.array([4.,4.5,5.,4.,4.5,5.,4.,4.5,5.,4.,4.5,5.,4.,4.5,5.,4.,4.5,5.,4.,4.5,5.,4.,4.5,5.,4.,4.5,5.]) - else: - expected_dfield_f = np.array([1.,1.5,2.,2.,2.5,3.,3.,3.5,4.,3.,3.5,4.,4.,4.5,5.,5.,5.5,6.,5.,5.5,6.,6.,6.5,7.,7.,7.5,8.]) - distri_vtx = PT.maia.getDistribution(PT.get_all_Zone_t(dist_tree)[0], 'Vertex')[1] - expected_dfield = expected_dfield_f[distri_vtx[0]:distri_vtx[1]] - - assert (dfield_vtx == expected_dfield).all() - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("from_api", [False, True]) -def test_nodes_to_centers(from_api, comm): - dist_tree = maia.factory.generate_dist_block([6,4,2], 'HEXA_8', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - # Create sol on partitions - for part in PT.get_all_Zone_t(part_tree): - gnum = PT.maia.getGlobalNumbering(part, 'Vertex')[1] - PT.new_FlowSolution('FSol', loc='Vertex', fields={'gnum': gnum}, parent=part) - - if from_api: - ML.nodes_to_centers(part_tree, comm, ["FSol"]) - else: - node_to_center = ML.NodeToCenter(part_tree, comm) - node_to_center.move_fields("FSol") - - maia.transfer.part_tree_to_dist_tree_only_labels(dist_tree, part_tree, ['FlowSolution_t'], comm) - dsol_cell = PT.get_node_from_name(dist_tree, 'FSol#Cell') - dfield_cell = PT.get_node_from_name(dsol_cell, 'gnum')[1] - - elt = PT.get_node_from_predicate(dist_tree, lambda n : PT.get_label(n) == 'Elements_t' \ - and PT.Element.CGNSName(n)=='HEXA_8') - ec = PT.get_child_from_name(elt, 'ElementConnectivity')[1] - expected_dfield = np.add.reduceat(ec, 8*np.arange(0,ec.size//8)) / 8. - - assert np.allclose(dfield_cell, expected_dfield) - -def test_nodes_to_centers_S(comm) : - dist_tree = maia.factory.generate_dist_block(4, 'S', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - zone = PT.get_all_Zone_t(part_tree)[0] - cx = PT.get_node_from_name(zone, 'CoordinateX')[1] - cy = PT.get_node_from_name(zone, 'CoordinateY')[1] - cz = PT.get_node_from_name(zone, 'CoordinateZ')[1] - PT.new_FlowSolution('FlowSolution', loc='Vertex', fields={'cX': cx, 'cY': cy, 'cZ': cz}, parent=zone) - expected = maia.algo.part.compute_cell_center(zone) - - ML.nodes_to_centers(part_tree, comm, ["FlowSolution"]) - sol_cell = PT.get_node_from_name(part_tree, 'FlowSolution#Cell') - for i, dir in enumerate(['X', 'Y', 'Z']): - field = PT.get_node_from_name(sol_cell, f'c{dir}')[1] - assert field.shape == (3,3,3) and field.dtype == float - assert np.allclose(field.flatten(order='F'), expected[i::3]) - - -def test_centers_to_node_S(comm) : - dist_tree = maia.factory.generate_dist_block(3, 'S', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - zone = PT.get_all_Zone_t(part_tree)[0] - cell_center = maia.algo.part.compute_cell_center(zone) - ccx = cell_center[0::3].reshape(PT.Zone.CellSize(zone), order='F') - ccy = cell_center[1::3].reshape(PT.Zone.CellSize(zone), order='F') - ccz = cell_center[2::3].reshape(PT.Zone.CellSize(zone), order='F') - PT.new_FlowSolution('FlowSolution', loc='CellCenter', fields={'cX': ccx, 'cY': ccy, 'cZ': ccz}, parent=zone) - - ML.centers_to_nodes(part_tree, comm, ["FlowSolution"]) - - expected_vtx = [[0.25, 0.5, 0.75, 0.25, 0.5, 0.75, 0.25, 0.5, 0.75, 0.25, 0.5, 0.75, 0.25, 0.5, - 0.75, 0.25, 0.5, 0.75, 0.25, 0.5, 0.75, 0.25, 0.5, 0.75, 0.25, 0.5, 0.75], - [0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25, 0.5, 0.5, - 0.5, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 0.75], - [0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5, 0.5, 0.5, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]] - sol_cell = PT.get_node_from_name(part_tree, 'FlowSolution#Vtx') - for i, dir in enumerate(['X', 'Y', 'Z']): - field = PT.get_node_from_name(sol_cell, f'c{dir}')[1] - assert field.shape == (3,3,3) and field.dtype == float - assert np.allclose(field.flatten(order='F'), expected_vtx[i]) diff --git a/maia/algo/part/test/test_multidom_gnum.py b/maia/algo/part/test/test_multidom_gnum.py deleted file mode 100644 index 9dc624be..00000000 --- a/maia/algo/part/test/test_multidom_gnum.py +++ /dev/null @@ -1,85 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -from maia.pytree.yaml import parse_yaml_cgns - -import maia.algo.part.multidom_gnum as MGM - -@pytest_parallel.mark.parallel(3) -def test_get_shifted_arrays(comm): - if comm.Get_rank() == 0: - arrays = [[np.array([1,8,4])], [np.array([3,6,1,9])]] - expected = [[np.array([1,8,4])], [np.array([13,16,11,19])]] - elif comm.Get_rank() == 1: - arrays = [[], [np.array([9,3,1,4]), np.array([8,6])]] - expected = [[], [np.array([19,13,11,14]), np.array([18,16])]] - elif comm.Get_rank() == 2: - arrays = [[np.array([10,2])], [np.empty(0)]] - expected = [[np.array([10,2])], [np.empty(0)]] - - offset, shifted_arrays = MGM._get_shifted_arrays(arrays, comm) - assert (offset == [0,10,19]).all() - for i in range(2): - for t1, t2 in zip(shifted_arrays[i], expected[i]): - assert (t1 == t2).all() - - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("unify_jns", [False, True]) -def test_get_mdom_gnum_vtx(unify_jns, comm): - if comm.Get_rank() == 0: - yt = """ - ZoneA.P0.N0 Zone_t: - ZoneType ZoneType_t "Unstructured": - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t [4,2,1,3,5]: - ZGC ZoneGridConnectivity_t: - matchAB.0 GridConnectivity_t "ZoneB.P1.N0": - GridConnectivityDonorName Descriptor_t "matchBA.0": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "Vertex": - PointList IndexArray_t [[4,5]]: - PointListDonor IndexArray_t [[2,1]]: - :CGNS#GlobalNumbering UserDefinedData_t: - Index DataArray_t [1,2]: - ZoneB.P0.N0 Zone_t: - ZoneType ZoneType_t "Unstructured": - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t [4,1]: - """ - if unify_jns: - expected_gn = [[np.array([3,2,1,6,7])], [np.array([5,4])]] - else: - expected_gn = [[np.array([4,2,1,3,5])], [np.array([9,6])]] - elif comm.Get_rank() == 1: - yt = """ - ZoneB.P1.N0 Zone_t: - ZoneType ZoneType_t "Unstructured": - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t [2,3]: - ZGC ZoneGridConnectivity_t: - matchBA.0 GridConnectivity_t "ZoneA.P0.N0": - GridConnectivityDonorName Descriptor_t "matchAB.0": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridLocation GridLocation_t "Vertex": - PointList IndexArray_t [[2,1]]: - PointListDonor IndexArray_t [[4,5]]: - :CGNS#GlobalNumbering UserDefinedData_t: - Index DataArray_t [1,2]: - """ - if unify_jns: - expected_gn = [[], [np.array([7,6])]] - else: - expected_gn = [[], [np.array([7,8])]] - - all_part_zones = parse_yaml_cgns.to_nodes(yt) - part_per_doms = {'Base/ZoneA' : [z for z in all_part_zones if 'ZoneA' in z[0]], - 'Base/ZoneB' : [z for z in all_part_zones if 'ZoneB' in z[0]]} - - global_gnum = MGM.get_mdom_gnum_vtx(part_per_doms, comm, unify_jns) - - for i_dom, parts in enumerate(part_per_doms.values()): - for i_part in range(len(parts)): - assert np.array_equal(global_gnum[i_dom][i_part], expected_gn[i_dom][i_part]) - diff --git a/maia/algo/part/test/test_point_cloud_utils.py b/maia/algo/part/test/test_point_cloud_utils.py deleted file mode 100644 index 79457864..00000000 --- a/maia/algo/part/test/test_point_cloud_utils.py +++ /dev/null @@ -1,100 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.factory import dcube_generator as DCG - -from maia.algo.part import point_cloud_utils as PCU - -def as_partitioned(zone): - PT.rm_nodes_from_name(zone, ":CGNS#Distribution") - predicate = lambda n : PT.get_label(n) in ['Zone_t', 'DataArray_t', 'IndexArray_t', 'IndexRange_t'] \ - and PT.get_value(n).dtype == np.int64 - for array in PT.iter_nodes_from_predicate(zone, predicate, explore='deep'): - array[1] = array[1].astype(np.int32) - -@pytest_parallel.mark.parallel(1) -def test_get_zone_ln_to_gn_from_loc(comm): - tree = DCG.dcube_generate(3, 1., [0.,0.,0.], comm) - zone = PT.get_all_Zone_t(tree)[0] - as_partitioned(zone) - vtx_gnum = np.arange(3**3) + 1 - cell_gnum = np.arange(2**3) + 1 - MT.newGlobalNumbering({'Vertex' : vtx_gnum, 'Cell' : cell_gnum}, parent=zone) - - assert (PCU._get_zone_ln_to_gn_from_loc(zone, 'Vertex') == vtx_gnum).all() - assert (PCU._get_zone_ln_to_gn_from_loc(zone, 'CellCenter') == cell_gnum).all() - -@pytest_parallel.mark.parallel(1) -def test_get_point_cloud(comm): - tree = DCG.dcube_generate(3, 1., [0.,0.,0.], comm) - zone = PT.get_all_Zone_t(tree)[0] - as_partitioned(zone) - PT.rm_nodes_from_label(zone, 'ZoneBC_t') - fs = PT.new_FlowSolution('MyOwnCoords', loc='CellCenter', parent=zone) - PT.new_DataArray('CoordinateX', 1*np.ones(8), parent=fs) - PT.new_DataArray('CoordinateY', 2*np.ones(8), parent=fs) - PT.new_DataArray('CoordinateZ', 3*np.ones(8), parent=fs) - vtx_gnum = np.arange(3**3) + 1 - cell_gnum = np.arange(2**3) + 1 - MT.newGlobalNumbering({'Vertex' : vtx_gnum, 'Cell' : cell_gnum}, parent=zone) - - expected_vtx_co = np.array([0., 0. , 0. , 0.5, 0. , 0. , 1., 0. , 0. , 0., 0.5, 0. , 0.5, 0.5, 0. , 1., 0.5, 0., - 0., 1. , 0. , 0.5, 1. , 0. , 1., 1. , 0. , 0., 0. , 0.5, 0.5, 0. , 0.5, 1., 0. , 0.5, - 0., 0.5, 0.5, 0.5, 0.5, 0.5, 1., 0.5, 0.5, 0., 1. , 0.5, 0.5, 1. , 0.5, 1., 1. , 0.5, - 0., 0. , 1. , 0.5, 0. , 1. , 1., 0. , 1. , 0., 0.5, 1. , 0.5, 0.5, 1. , 1., 0.5, 1., - 0., 1. , 1. , 0.5, 1. , 1. , 1., 1. , 1. ]) - expected_cell_co = np.array( - [0.25, 0.25, 0.25, 0.75, 0.25, 0.25, 0.25, 0.75, 0.25, 0.75, 0.75, 0.25, 0.25, 0.25, - 0.75, 0.75, 0.25, 0.75, 0.25, 0.75, 0.75, 0.75, 0.75, 0.75]) - - - coords, gnum = PCU.get_point_cloud(zone, 'Vertex') - assert (gnum == vtx_gnum).all() - assert (coords == expected_vtx_co).all() - - coords, gnum = PCU.get_point_cloud(zone, 'CellCenter') - assert (gnum == cell_gnum).all() - assert (coords == expected_cell_co).all() - - coords, gnum = PCU.get_point_cloud(zone, 'MyOwnCoords') - assert (gnum == cell_gnum).all() - assert (coords == np.tile([1,2,3], 8)).all() #Repeat motif - - with pytest.raises(RuntimeError): - coords, gnum = PCU.get_point_cloud(zone, 'FaceCenter') - -def test_extract_sub_cloud(): - coords = np.array([0,0,0, .5,0,0, 1,0,0, 0,1,0, .5,1,0, 1,1,0]) - lngn = np.array([42,9,1,4,55,3], pdm_gnum_dtype) - - sub_coords, sub_lngn = PCU.extract_sub_cloud(coords, lngn, np.empty(0, np.int32)) - assert sub_coords.size == sub_lngn.size == 0 - sub_coords, sub_lngn = PCU.extract_sub_cloud(coords, lngn, np.array([0,1,2,3,4,5], np.int32)) - assert (sub_coords == coords).all() - assert (sub_lngn == lngn).all() - sub_coords, sub_lngn = PCU.extract_sub_cloud(coords, lngn, np.array([1,3], np.int32)) - assert (sub_coords == [.5,0,0, 0,1,0]).all() - assert (sub_lngn == [9,4]).all() - sub_coords, sub_lngn = PCU.extract_sub_cloud(coords, lngn, np.array([3,1], np.int32)) - assert (sub_coords == [0,1,0, .5,0,0]).all() - assert (sub_lngn == [4,9]).all() - -@pytest_parallel.mark.parallel(2) -def test_create_sub_numbering(comm): - if comm.Get_rank() == 0: - lngn_l = [ np.array([3,9], pdm_gnum_dtype), np.array([], pdm_gnum_dtype) ] - expected_sub_lngn_l = [ np.array([1,5]), np.array([]) ] - elif comm.Get_rank() == 1: - lngn_l = [ np.array([7,5,6], pdm_gnum_dtype) ] - expected_sub_lngn_l = [ np.array([4,2,3]) ] - - sub_gnum_l = PCU.create_sub_numbering(lngn_l, comm) - - for sub_gnum, expected_sub_lngn in zip(sub_gnum_l, expected_sub_lngn_l): - assert (sub_gnum == expected_sub_lngn).all() - diff --git a/maia/algo/part/test/test_wall_distance.py b/maia/algo/part/test/test_wall_distance.py deleted file mode 100644 index 2fbb7077..00000000 --- a/maia/algo/part/test/test_wall_distance.py +++ /dev/null @@ -1,227 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT - -import maia -from maia.pytree.yaml import parse_yaml_cgns - -from maia.algo.part import wall_distance as WD - -def test_detect_wall_families(): - yt = """ - BaseA CGNSBase_t: - SomeWall Family_t: - FamilyBC FamilyBC_t "BCWallViscous": - SomeNoWall Family_t: - FamilyBC FamilyBC_t "BCFarfield": - BaseB CGNSBase_t: - SomeOtherWall Family_t: - FamilyBC FamilyBC_t "BCWall": - BaseC CGNSBase_t: - """ - tree = parse_yaml_cgns.to_cgns_tree(yt) - - assert WD.detect_wall_families(tree) == ['SomeWall', 'SomeOtherWall'] - - -# For U, we reuse the meshes defined in test_interpolate -from maia.algo.part.test.test_interpolate import src_part_0, src_part_1 - -@pytest.mark.skipif(not maia.pdma_enabled, reason="Require ParaDiGMA") -@pytest.mark.parametrize("perio", [True, False]) -@pytest_parallel.mark.parallel(2) -def test_wall_distance_U(perio, comm): - if comm.Get_rank() == 0: - part_tree = parse_yaml_cgns.to_cgns_tree(src_part_0) - expected_wd = [0.75, 0.25, 0.25, 0.75] - expected_gnum = [1, 1, 2, 2] - elif comm.Get_rank() == 1: - part_tree = parse_yaml_cgns.to_cgns_tree(src_part_1) - expected_wd = [0.75, 0.25, 0.75, 0.25] - expected_gnum = [3, 3, 4, 4] - base = PT.get_all_CGNSBase_t(part_tree)[0] - base_family = PT.new_Family('WALL', family_bc='BCWall', parent=base) - zone = PT.get_all_Zone_t(part_tree)[0] - zone[0] += f'.P{comm.Get_rank()}.N0' - - # Add BC - zone_bc = parse_yaml_cgns.to_node(""" - ZoneBC ZoneBC_t: - BC BC_t "FamilySpecified": - PointList IndexArray_t [[13,14]]: - GridLocation GridLocation_t "FaceCenter": - FamilyName FamilyName_t "WALL": - """) - PT.add_child(zone, zone_bc) - - # Test with propagation method + default out_fs_name - if perio: - with pytest.warns(RuntimeWarning): - WD.compute_wall_distance(part_tree, comm, method="propagation", perio=perio) - else: - WD.compute_wall_distance(part_tree, comm, method="propagation", perio=perio) - - fs = PT.get_child_from_name(zone, 'WallDistance') - assert fs is not None and PT.Subset.GridLocation(fs) == 'CellCenter' - for array in PT.iter_children_from_label(fs, 'DataArray_t'): - assert array[1].shape == (4,) - assert (PT.get_child_from_name(fs, 'TurbulentDistance')[1] == expected_wd).all() - assert (PT.get_child_from_name(fs, 'ClosestEltGnum')[1] == expected_gnum).all() - - #Test with cloud method + custom fs name - PT.rm_nodes_from_name(part_tree, 'WallDistance') - WD.compute_wall_distance(part_tree, comm, method="cloud", out_fs_name='MyWallDistance', perio=perio) - - fs = PT.get_child_from_name(zone, 'MyWallDistance') - assert fs is not None and PT.Subset.GridLocation(fs) == 'CellCenter' - for array in PT.iter_children_from_label(fs, 'DataArray_t'): - assert array[1].shape == (4,) - assert (PT.get_child_from_name(fs, 'TurbulentDistance')[1] == expected_wd).all() - assert (PT.get_child_from_name(fs, 'ClosestEltGnum')[1] == expected_gnum).all() - -@pytest_parallel.mark.parallel(2) -def test_projection_to(comm): - if comm.Get_rank() == 0: - part_tree = parse_yaml_cgns.to_cgns_tree(src_part_0) - expected_wd = [0.75, 0.25, 0.25, 0.75] - expected_gnum = [1, 1, 2, 2] - elif comm.Get_rank() == 1: - part_tree = parse_yaml_cgns.to_cgns_tree(src_part_1) - expected_wd = [0.75, 0.25, 0.75, 0.25] - expected_gnum = [3, 3, 4, 4] - base = PT.get_all_CGNSBase_t(part_tree)[0] - base_family = PT.new_Family('WALL', family_bc='BCWall', parent=base) - zone = PT.get_all_Zone_t(part_tree)[0] - zone[0] += f'.P{comm.Get_rank()}.N0' - - # Add BC - zone_bc = parse_yaml_cgns.to_node(""" - ZoneBC ZoneBC_t: - BC BC_t "FamilySpecified": - PointList IndexArray_t [[13,14]]: - GridLocation GridLocation_t "FaceCenter": - FamilyName FamilyName_t "WALL": - """) - PT.add_child(zone, zone_bc) - - WD.compute_projection_to(part_tree, lambda n: PT.get_label(n) == 'BC_t', comm) - PT.print_tree(part_tree) - - fs = PT.get_child_from_name(zone, 'SurfDistance') - assert fs is not None and PT.Subset.GridLocation(fs) == 'CellCenter' - assert (PT.get_child_from_name(fs, 'Distance')[1] == expected_wd).all() - assert (PT.get_child_from_name(fs, 'ClosestEltGnum')[1] == expected_gnum).all() - -@pytest_parallel.mark.parallel(2) -def test_walldistance_elts(comm): - tree = maia.factory.generate_dist_block(3, 'TETRA_4', comm) - # Set some BC wall - for name in ['Xmin', 'Ymin', 'Zmin']: - bc = PT.get_node_from_name(tree, 'Xmin') - PT.set_value(bc, 'BCWall') - - ptree = maia.factory.partition_dist_tree(tree, comm) - WD.compute_wall_distance(ptree, comm) - maia.transfer.part_tree_to_dist_tree_all(tree, ptree, comm) - - if comm.Get_rank() == 0: - expected_wd = [0.125,0.375,0.125,0.375,0.25 ,0.875,0.875,0.625,0.625,0.75,0.375,0.375, - 0.125,0.125,0.25 ,0.625,0.875,0.625,0.875,0.75] - expected_gnum = [1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3] - elif comm.Get_rank() == 1: - expected_wd = [0.375,0.375,0.125,0.125,0.25, 0.625,0.875,0.625,0.875,0.75,0.125,0.375, - 0.125,0.375,0.25, 0.875,0.875,0.625,0.625,0.75] - expected_gnum = [5, 5, 5, 6, 5, 5, 6, 5, 5, 5, 7, 7, 8, 7, 7, 7, 8, 7, 7, 7] - - - assert (PT.get_node_from_name(tree, 'TurbulentDistance')[1] == expected_wd).all() - assert (PT.get_node_from_name(tree, 'ClosestEltGnum')[1] == expected_gnum).all() - - -@pytest_parallel.mark.parallel(2) -def test_walldistance_perio(comm): - #Case generation - dist_treeU = maia.factory.generate_dist_block(3, "Poly", comm, edge_length=2.) - coordX, coordY, coordZ = PT.Zone.coordinates(PT.get_node_from_label(dist_treeU, 'Zone_t')) - coordX += coordY - coordY -= coordZ - - for bc in PT.get_nodes_from_label(dist_treeU, "BC_t"): - family_bc = "BCWall" if PT.get_name(bc) == 'Ymin' else None - PT.new_Family(f"Fam_{PT.get_name(bc)}", family_bc=family_bc, parent=PT.get_child_from_label(dist_treeU, "CGNSBase_t")) - PT.new_node(name='FamilyName', label='FamilyName_t', value=f"Fam_{PT.get_name(bc)}", parent=bc) - - maia.algo.dist.connect_1to1_families(dist_treeU, ('Fam_Xmin', 'Fam_Xmax'), comm, - periodic={'translation' : np.array([2.,0.,0.])}) - - maia.algo.dist.connect_1to1_families(dist_treeU, ('Fam_Zmin', 'Fam_Zmax'), comm, - periodic={'translation' : np.array([0.,-2.,2.])}) - - zone_paths1 = maia.pytree.predicates_to_paths(dist_treeU, "CGNSBase_t/Zone_t") - jn_paths_for_dupl1 = [['Base/zone/ZoneGridConnectivity/Xmin_0'],['Base/zone/ZoneGridConnectivity/Xmax_0']] - maia.algo.dist.duplicate_from_periodic_jns(dist_treeU, zone_paths1, jn_paths_for_dupl1, 1, comm) - - zone_paths2 = maia.pytree.predicates_to_paths(dist_treeU, "CGNSBase_t/Zone_t") - jn_paths_for_dupl2 = [['Base/zone.D0/ZoneGridConnectivity/Zmin_0','Base/zone.D1/ZoneGridConnectivity/Zmin_0'], - ['Base/zone.D0/ZoneGridConnectivity/Zmax_0','Base/zone.D1/ZoneGridConnectivity/Zmax_0']] - maia.algo.dist.duplicate_from_periodic_jns(dist_treeU, zone_paths2, jn_paths_for_dupl2, 1, comm) - - part_tree = maia.factory.partition_dist_tree(dist_treeU, comm) - - # Test with family specification - WD.compute_wall_distance(part_tree, comm) - - expected_wd = [0.35355339, 0.35355339, 1.06066017, 1.06066017, - 0.35355339, 0.35355339, 1.06066017, 1.06066017] - if comm.rank == 0: - expected_gnum = [[ 1, 4, 3, 126, 2, 3, 92, 37], - [37, 6, 5, 98, 38, 5, 98, 5]] - expected_dom_id = [[0, 0, 0, 3, 0, 0, 2, 1], - [1, 0, 0, 2, 1, 0, 2, 0]] - elif comm.rank == 1: - expected_gnum = [[ 73, 76, 75, 38, 74, 75, 4, 109], - [109, 78, 77, 6, 110, 77, 6, 77]] - expected_dom_id = [[2, 2, 2, 1, 2, 2, 0, 3], - [3, 2, 2, 0, 3, 2, 0, 2]] - - for z, zone in enumerate(PT.get_all_Zone_t(part_tree)): - fs = PT.get_child_from_name_and_label(zone, 'WallDistance', 'FlowSolution_t') - assert fs is not None and PT.Subset.GridLocation(fs) == 'CellCenter' - assert np.allclose(PT.get_value(PT.get_child_from_name(fs, 'TurbulentDistance')), expected_wd, rtol=1e-10) - assert (PT.get_value(PT.get_child_from_name(fs, 'ClosestEltGnum')) == expected_gnum[z]).all() - assert (PT.get_value(PT.get_child_from_name(fs, 'ClosestEltDomId')) == expected_dom_id[z]).all() - -@pytest_parallel.mark.parallel(2) -def test_walldistance_vtx(comm): - if comm.Get_rank() == 0: - part_tree = parse_yaml_cgns.to_cgns_tree(src_part_0) - expected_wd = [1, 0.5, 0, 1., 0.5, 0, 1., 0.5, 0, 1., 0.5, 0, 1., 0.5, 0, 1., 0.5, 0 ] - expected_gnum = [1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,2,2,2] - elif comm.Get_rank() == 1: - part_tree = parse_yaml_cgns.to_cgns_tree(src_part_1) - expected_wd = [1, 0.5, 0, 1., 0.5, 0, 1., 0.5, 0, 1., 0.5, 0, 1., 0.5, 0, 1., 0.5, 0 ] - expected_gnum = [3,3,3,3,3,3,4,4,4,1,1,1,1,1,1,2,2,2] - base = PT.get_all_CGNSBase_t(part_tree)[0] - base_family = PT.new_Family('WALL', family_bc='BCWall', parent=base) - zone = PT.get_all_Zone_t(part_tree)[0] - zone[0] += f'.P{comm.Get_rank()}.N0' - - # Add BC - zone_bc = parse_yaml_cgns.to_node(""" - ZoneBC ZoneBC_t: - BC BC_t "FamilySpecified": - PointList IndexArray_t [[13,14]]: - GridLocation GridLocation_t "FaceCenter": - FamilyName FamilyName_t "WALL": - """) - PT.add_child(zone, zone_bc) - - WD.compute_wall_distance(part_tree, comm, method="cloud", point_cloud="Vertex", out_fs_name='MyWallDistance') - - fs = PT.get_child_from_name(zone, 'MyWallDistance') - assert fs is not None and PT.Subset.GridLocation(fs) == 'Vertex' - assert (PT.get_child_from_name(fs, 'TurbulentDistance')[1] == expected_wd).all() - assert (PT.get_child_from_name(fs, 'ClosestEltGnum')[1] == expected_gnum).all() - diff --git a/maia/algo/part/wall_distance.py b/maia/algo/part/wall_distance.py deleted file mode 100644 index 66b9b31a..00000000 --- a/maia/algo/part/wall_distance.py +++ /dev/null @@ -1,428 +0,0 @@ -import time -from mpi4py import MPI -import numpy as np -import warnings - -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import np_utils -from maia.utils import logging as mlog -from maia import transfer as TE -from maia.factory.dist_from_part import discover_nodes_from_matching - -from .point_cloud_utils import get_point_cloud -from maia.algo.part.extract_boundary import extract_surf_from_bc -from maia.algo.part.geometry import compute_cell_center -from maia.transfer import utils as tr_utils - -BC_WALLS = ['BCWall', 'BCWallViscous', 'BCWallViscousHeatFlux', 'BCWallViscousIsothermal'] - -def _are_same_perio_abs(first, second): - """ Return True if the two periodic transformation are the same in absolute value""" - first_center, first_angle, first_trans = first - second_center, second_angle, second_trans = second - if np.allclose(first_center, second_center): - if np.allclose(first_angle, second_angle) and np.allclose(first_trans, second_trans): - return True - if np.allclose(first_angle, -second_angle) and np.allclose(first_trans, -second_trans): - return True - return False - -def detect_wall_families(tree, bcwalls=BC_WALLS): - """ - Return the list of Families having a FamilyBC_t node whose value is in bcwalls list - """ - fam_query = lambda n : PT.get_label(n) == 'Family_t' and \ - PT.get_child_from_label(n, 'FamilyBC_t') is not None and \ - PT.get_value(PT.get_child_from_label(n, 'FamilyBC_t')) in bcwalls - return [PT.get_name(family) for family in PT.iter_children_from_predicates(tree, ['CGNSBase_t', fam_query])] - - -# ------------------------------------------------------------------------ -class WallDistance: - """ Implementation of wall distance. See compute_wall_distance for full documentation. - """ - - def __init__(self, part_tree, bc_predicate, mpi_comm, *, method="cloud", point_cloud='CellCenter', out_fs_name='WallDistance', perio=True): - self.part_tree = part_tree - self.bc_predicate = bc_predicate - self.mpi_comm = mpi_comm - assert method in ["cloud", "propagation"] - self.method = method - self.point_cloud = point_cloud - self.out_fs_n = out_fs_name - - self._walldist = None - self._keep_alive = [] - self._n_vtx_bnd_tot_idx = [0] - self._n_face_bnd_tot_idx = [0] - - self.perio = perio - self.periodicities = [] - - def _shift_id_and_push_in_global_list(self, parts_datas, all_parts_datas, i_dom): - - face_vtx_bnd_z, face_vtx_bnd_idx_z, face_ln_to_gn_z, vtx_bnd_z, vtx_ln_to_gn_z = parts_datas - face_vtx_bnd_l, face_vtx_bnd_idx_l, face_ln_to_gn_l, vtx_bnd_l, vtx_ln_to_gn_l = all_parts_datas - - #Find the maximal vtx/face id for this initial domain - n_face_bnd_t = 0 - for face_ln_to_gn in face_ln_to_gn_z: - n_face_bnd_t = max(n_face_bnd_t, np.max(face_ln_to_gn, initial=0)) - n_face_bnd_t = self.mpi_comm.allreduce(n_face_bnd_t, op=MPI.MAX) - self._n_face_bnd_tot_idx.append(self._n_face_bnd_tot_idx[-1] + n_face_bnd_t) - - n_vtx_bnd_t = 0 - for vtx_ln_to_gn in vtx_ln_to_gn_z: - n_vtx_bnd_t = max(n_vtx_bnd_t, np.max(vtx_ln_to_gn, initial=0)) - n_vtx_bnd_t = self.mpi_comm.allreduce(n_vtx_bnd_t, op=MPI.MAX) - self._n_vtx_bnd_tot_idx.append(self._n_vtx_bnd_tot_idx[-1] + n_vtx_bnd_t) - - #Shift the face and vertex lngn because PDM does not manage multiple domain. This will avoid - # overlapping face / vtx coming from different domain but having same id - face_ln_to_gn_z = [face_ln_to_gn + self._n_face_bnd_tot_idx[i_dom] for face_ln_to_gn in face_ln_to_gn_z] - vtx_ln_to_gn_z = [vtx_ln_to_gn + self._n_vtx_bnd_tot_idx[i_dom] for vtx_ln_to_gn in vtx_ln_to_gn_z] - - #Extended global lists - face_vtx_bnd_l.extend(face_vtx_bnd_z) - face_vtx_bnd_idx_l.extend(face_vtx_bnd_idx_z) - vtx_bnd_l.extend(vtx_bnd_z) - face_ln_to_gn_l.extend(face_ln_to_gn_z) - vtx_ln_to_gn_l.extend(vtx_ln_to_gn_z) - - def _dupl_shift_id_and_push_in_global_list(self, parts_datas, all_parts_datas, i_dom, perio): - - vtx_bnd_z = parts_datas[3] - vtx_bnd_dupl_z = [] - for vtx_bnd in vtx_bnd_z: - cx = vtx_bnd[0::3] - cy = vtx_bnd[1::3] - cz = vtx_bnd[2::3] - cx, cy, cz = np_utils.transform_cart_vectors(cx, cy, cz, perio[2], perio[0], perio[1]) #Perio is center, angle, trans - vtx_bnd_dupl_z.append(np_utils.interweave_arrays([cx, cy, cz])) - - dupl_parts_data = [l for l in parts_datas] - dupl_parts_data[3] = vtx_bnd_dupl_z #Udpate with duplicated coords - - self._shift_id_and_push_in_global_list(dupl_parts_data, all_parts_datas, i_dom) - return dupl_parts_data - - def _setup_surf_mesh(self, parts_per_dom, comm): - """ - Setup the surfacic mesh for wall distance computing - """ - #This will concatenate part data of all initial domains - face_vtx_bnd_l = [] - face_vtx_bnd_idx_l = [] - face_ln_to_gn_l = [] - vtx_bnd_l = [] - vtx_ln_to_gn_l = [] - - all_parts_datas = [face_vtx_bnd_l, face_vtx_bnd_idx_l, face_ln_to_gn_l, vtx_bnd_l, vtx_ln_to_gn_l] - - i_dom = -1 - for part_zones in parts_per_dom: - - i_dom += 1 - parts_datas = extract_surf_from_bc(part_zones, self.bc_predicate, comm) - self._shift_id_and_push_in_global_list(parts_datas, all_parts_datas, i_dom) - - if self.perio: - parts_surf_to_dupl_l = [parts_datas] - for perio_val in self.periodicities: - perio_val_opp = (perio_val[0], -perio_val[1], -perio_val[2]) #Center, angle, translation - - parts_surf_to_dupl_next_l = [] - for parts_surf_to_dupl in parts_surf_to_dupl_l: - parts_surf_to_dupl_next_l.append(parts_surf_to_dupl) - i_dom += 1 - dupl_parts_surf = self._dupl_shift_id_and_push_in_global_list( - parts_surf_to_dupl, all_parts_datas, i_dom, perio_val) - parts_surf_to_dupl_next_l.append(dupl_parts_surf) - - i_dom += 1 - dupl_parts_surf = self._dupl_shift_id_and_push_in_global_list( - parts_surf_to_dupl, all_parts_datas, i_dom, perio_val_opp) - parts_surf_to_dupl_next_l.append(dupl_parts_surf) - - parts_surf_to_dupl_l = parts_surf_to_dupl_next_l - - n_part = len(vtx_bnd_l) - for i in range(n_part): - # Keep numpy alive - for array in (face_vtx_bnd_l[i], face_vtx_bnd_idx_l[i], face_ln_to_gn_l[i], vtx_bnd_l[i], vtx_ln_to_gn_l[i],): - self._keep_alive.append(array) - - #Get global data (total number of faces / vertices) - #This create the surf_mesh objects in PDM, thus it must be done before surf_mesh_part_set - self._walldist.surf_mesh_global_data_set() - - #Setup partitions - for i_part in range(n_part): - n_face_bnd = face_vtx_bnd_idx_l[i_part].shape[0]-1 - n_vtx_bnd = vtx_ln_to_gn_l[i_part].shape[0] - self._walldist.surf_mesh_part_set(i_part, n_face_bnd, - face_vtx_bnd_idx_l[i_part], - face_vtx_bnd_l[i_part], - face_ln_to_gn_l[i_part], - n_vtx_bnd, - vtx_bnd_l[i_part], - vtx_ln_to_gn_l[i_part]) - - def _setup_vol_mesh(self, i_domain, part_zones, comm): - """ - Setup the volumic mesh for wall distance computing (only for propagation method) - """ - #Setup global data - self._walldist.vol_mesh_global_data_set() - - for i_part, part_zone in enumerate(part_zones): - - vtx_coords = np_utils.interweave_arrays(PT.Zone.coordinates(part_zone)) - face_vtx_idx, face_vtx, _ = PT.Zone.ngon_connectivity(part_zone) - - nface = PT.Zone.NFaceNode(part_zone) - cell_face_idx = PT.get_value(PT.get_child_from_name(nface, 'ElementStartOffset')) - cell_face = PT.get_value(PT.get_child_from_name(nface, 'ElementConnectivity')) - - vtx_ln_to_gn, _, face_ln_to_gn, cell_ln_to_gn = TE.utils.get_entities_numbering(part_zone) - - n_vtx = vtx_ln_to_gn .shape[0] - n_cell = cell_ln_to_gn.shape[0] - n_face = face_ln_to_gn.shape[0] - - center_cell = compute_cell_center(part_zone) - assert(center_cell.size == 3*n_cell) - - # Keep numpy alive - for array in (cell_face_idx, cell_face, cell_ln_to_gn, face_vtx_idx, face_vtx, face_ln_to_gn, \ - vtx_coords, vtx_ln_to_gn, center_cell): - self._keep_alive.append(array) - - self._walldist.vol_mesh_part_set(i_part, - n_cell, cell_face_idx, cell_face, center_cell, cell_ln_to_gn, - n_face, face_vtx_idx, face_vtx, face_ln_to_gn, - n_vtx, vtx_coords, vtx_ln_to_gn) - - def _get(self, i_domain, part_zones): - """ - Get results after wall distance computation and store it in the FlowSolution - node of name self.out_fs_name - """ - for i_part, part_zone in enumerate(part_zones): - - fields = self._walldist.get(i_domain, i_part) if self.method == "cloud" else self._walldist.get(i_part) - - # Retrieve location - if self.point_cloud in ['Vertex', 'CellCenter']: - output_loc = self.point_cloud - else: - output_loc = PT.Subset.GridLocation(PT.get_child_from_name(part_zone, self.point_clouds)) - # Test if FlowSolution already exists or create it - fs_node = PT.get_child_from_name(part_zone, self.out_fs_n) - if fs_node is None: - fs_node = PT.new_FlowSolution(name=self.out_fs_n, loc=output_loc, parent=part_zone) - assert PT.Subset.GridLocation(fs_node) == output_loc - if output_loc == "CellCenter": - shape = PT.Zone.CellSize(part_zone) - elif output_loc == "Vertex": - shape = PT.Zone.VertexSize(part_zone) - else: - raise RuntimeError("Unmanaged output location") - - # Wall distance - wall_dist = np.sqrt(fields['ClosestEltDistance']) - PT.new_DataArray('Distance', value=wall_dist.reshape(shape,order='F'), parent=fs_node) - - # Closest projected element - closest_elt_proj = np.copy(fields['ClosestEltProjected']) - PT.new_DataArray('ClosestEltProjectedX', closest_elt_proj[0::3].reshape(shape,order='F'), parent=fs_node) - PT.new_DataArray('ClosestEltProjectedY', closest_elt_proj[1::3].reshape(shape,order='F'), parent=fs_node) - PT.new_DataArray('ClosestEltProjectedZ', closest_elt_proj[2::3].reshape(shape,order='F'), parent=fs_node) - - # Closest gnum element (face) - closest_elt_gnum = np.copy(fields['ClosestEltGnum']) - PT.new_DataArray('ClosestEltGnum', closest_elt_gnum.reshape(shape,order='F'), parent=fs_node) - - # Find domain to which the face belongs (mainly for debug) - n_face_bnd_tot_idx = np.array(self._n_face_bnd_tot_idx, dtype=closest_elt_gnum.dtype) - closest_surf_domain = np.searchsorted(n_face_bnd_tot_idx, closest_elt_gnum-1, side='right') -1 - closest_surf_domain = closest_surf_domain.astype(closest_elt_gnum.dtype) - closest_elt_gnuml = closest_elt_gnum - n_face_bnd_tot_idx[closest_surf_domain] - if self.perio: - closest_surf_domain = closest_surf_domain//(3**(len(self.periodicities))) - PT.new_DataArray("ClosestEltDomId", value=closest_surf_domain.reshape(shape,order='F'), parent=fs_node) - PT.new_DataArray("ClosestEltLocGnum", value=closest_elt_gnuml.reshape(shape,order='F'), parent=fs_node) - - - - def compute(self): - """ - Prepare, compute and get wall distance - """ - - #Get a skeleton tree including only Base, Zones - skeleton_tree = PT.new_CGNSTree() - discover_nodes_from_matching(skeleton_tree, [self.part_tree], 'CGNSBase_t/Zone_t', self.mpi_comm, - merge_rule = lambda path: MT.conv.get_part_prefix(path)) - - # Group partitions by original dist domain - parts_per_dom = list() - for zone_path in PT.predicates_to_paths(skeleton_tree, 'CGNSBase_t/Zone_t'): - parts_per_dom.append(TE.utils.get_partitioned_zones(self.part_tree, zone_path)) - assert len(parts_per_dom) >= 1 - - - if self.method == "cloud": - is_gc_perio = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] \ - and PT.GridConnectivity.isperiodic(n) - gc_predicate = ['ZoneGridConnectivity_t', is_gc_perio] - - # Recover existing periodicities - for dist_zone_path in PT.predicates_to_paths(skeleton_tree, 'CGNSBase_t/Zone_t'): - dist_zone = PT.get_node_from_path(skeleton_tree, dist_zone_path) - part_zones = tr_utils.get_partitioned_zones(self.part_tree, dist_zone_path) - discover_nodes_from_matching(dist_zone, part_zones, gc_predicate, self.mpi_comm, - child_list=['GridConnectivityProperty_t', 'GridConnectivityType_t'], - merge_rule=lambda path: MT.conv.get_split_prefix(path), get_value='leaf') - - all_periodicities, _ = PT.Tree.find_periodic_jns(skeleton_tree) - # Filter periodicities to get only one over two jns - for perio_val in all_periodicities: - for u_perio in self.periodicities: - if _are_same_perio_abs(perio_val, u_perio): - break - else: - self.periodicities.append(perio_val) - if len(self.periodicities) == 0: - self.perio = False #Disable perio to avoid unecessary loops - - elif self.perio: #Propagation + perio : not managed - warnings.warn("WallDistance do not manage periodicities except for 'cloud' method", RuntimeWarning, stacklevel=2) - self.perio = False - - # Create walldist structure - # Multidomain is not managed for n_part_surf, n_part_surf is the total of partitions - n_part_surf = sum([len(part_zones) for part_zones in parts_per_dom]) - if self.method == "propagation": - if len(parts_per_dom) > 1: - raise NotImplementedError("Wall_distance computation with method 'propagation' does not support multiple domains") - self._walldist = PDM.DistCellCenterSurf(self.mpi_comm, n_part_surf, n_part_vol=1) - elif self.method == "cloud": - n_part_per_cloud = [len(part_zones) for part_zones in parts_per_dom] - if self.perio: - n_part_surf = n_part_surf*3**(len(self.periodicities)) - self._walldist = PDM.DistCloudSurf(self.mpi_comm, 1, n_part_surf, point_clouds=n_part_per_cloud) - - self._setup_surf_mesh(parts_per_dom, self.mpi_comm) - if self._n_face_bnd_tot_idx[-1] == 0: - return -1 # No surface found - - # Prepare mesh depending on method - if self.method == "cloud": - for i_domain, part_zones in enumerate(parts_per_dom): - for i_part, part_zone in enumerate(part_zones): - points, points_lngn = get_point_cloud(part_zone, self.point_cloud) - self._keep_alive.extend([points, points_lngn]) - self._walldist.cloud_set(i_domain, i_part, points_lngn.shape[0], points, points_lngn) - - elif self.method == "propagation": - for i_domain, part_zones in enumerate(parts_per_dom): - self._walldist.n_part_vol = len(part_zones) - if len(part_zones) > 0 and PT.Zone.Type(part_zones[0]) != 'Unstructured': - raise NotImplementedError("Wall_distance computation with method 'propagation' does not support structured blocks") - self._setup_vol_mesh(i_domain, part_zones, self.mpi_comm) - - #Compute - self._walldist.compute() - - # Get results -- OK because name of method is the same for 2 PDM objects - for i_domain, part_zones in enumerate(parts_per_dom): - self._get(i_domain, part_zones) - - # Free unnecessary numpy - del self._keep_alive - - def dump_times(self): - self._walldist.dump_times() - - -# ------------------------------------------------------------------------ -def compute_projection_to(part_tree, bc_predicate, comm, point_cloud='CellCenter', out_fs_name='SurfDistance', **options): - - start = time.time() - - walldist = WallDistance(part_tree, bc_predicate, comm, point_cloud=point_cloud, out_fs_name=out_fs_name, **options) - out = walldist.compute() - end = time.time() - if out == -1: - mlog.error(f"Projection computing failed because no BC_t matches the given predicate") - else: - mlog.info(f"Projection computed ({end-start:.2f} s)") - -def compute_wall_distance(part_tree, comm, point_cloud='CellCenter', out_fs_name='WallDistance', **options): - """Compute wall distances and add it in tree. - - For each volumic point, compute the distance to the nearest face belonging to a BC of kind wall. - Computation can be done using "cloud" or "propagation" method. - - Note: - Propagation method requires ParaDiGMa access and is only available for unstructured cell centered - NGon connectivities grids. In addition, partitions must have been created from a single initial domain - with this method. - - Tree is modified inplace: computed distance are added in a FlowSolution container whose - name can be specified with out_fs_name parameter. - - The following optional parameters can be used to control the underlying method: - - - ``method`` ({'cloud', 'propagation'}): Choice of the geometric method. Defaults to ``'cloud'``. - - ``perio`` (bool): Take into account periodic connectivities. Defaults to ``True``. - Only available when method=cloud. - - Args: - part_tree (CGNSTree): Input partitionned tree - comm (MPIComm): MPI communicator - point_cloud (str, optional): Points to project on the surface. Can either be one of - "CellCenter" or "Vertex" (coordinates are retrieved from the mesh) or the name of a FlowSolution - node in which coordinates are stored. Defaults to CellCenter. - out_fs_name (str, optional): Name of the output FlowSolution_t node storing wall distance data. - **options: Additional options related to geometric method (see above) - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #compute_wall_distance@start - :end-before: #compute_wall_distance@end - :dedent: 2 - """ - - try: - options.pop('families') - except KeyError: - pass - else: - warnings.warn("Parameter families is deprecated; wall-like BC_t are automatically detected", - DeprecationWarning, stacklevel=2) - - start = time.time() - - # Retrieve Wall Families (warning -- if we have a Family_t appearing under two bases - # with the same name, it can be wrongly selected) - wall_bc_families = detect_wall_families(part_tree) - is_wall_bc = lambda n : PT.get_value(n) in BC_WALLS or \ - any([PT.predicate.belongs_to_family(n, wall_bc_family) for wall_bc_family in wall_bc_families]) - - walldist = WallDistance(part_tree, is_wall_bc, comm, point_cloud=point_cloud, out_fs_name=out_fs_name, **options) - out = walldist.compute() - end = time.time() - if out == -1: - mlog.error(f"Wall distance computing failed because no wall-like BC_t have been found in tree") - else: - mlog.info(f"Wall distance computed ({end-start:.2f} s)") - for zone in PT.iter_all_Zone_t(part_tree): #Rename Distance -> TurbulentDistance - node = PT.get_node_from_path(zone, out_fs_name+"/Distance") - PT.set_name(node, 'TurbulentDistance') - diff --git a/maia/algo/seq/__init__.py b/maia/algo/seq/__init__.py deleted file mode 100644 index 5ca1d282..00000000 --- a/maia/algo/seq/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Sequential algorithms for pytrees -""" - -from .compatibility import poly_new_to_old, poly_old_to_new, enforce_ngon_pe_local diff --git a/maia/algo/seq/compatibility.py b/maia/algo/seq/compatibility.py deleted file mode 100644 index 487b0492..00000000 --- a/maia/algo/seq/compatibility.py +++ /dev/null @@ -1,194 +0,0 @@ -from cmaia import tree_algo as ctree_algo -from maia.algo.apply_function_to_nodes import zones_iterator - -import numpy as np -import maia -import maia.pytree as PT - -from maia.utils import np_utils - -def indexed_to_interleaved_connectivity(node): - offset = PT.get_child_from_name(node, 'ElementStartOffset') - connec = PT.get_child_from_name(node, 'ElementConnectivity') - - connec[1] = np_utils.indexed_to_interlaced(offset[1], connec[1]) - PT.rm_child(node, offset) - -def interlaced_to_indexed_connectivity(node): - n_elem = PT.Element.Size(node) - connec = PT.get_child_from_name(node, 'ElementConnectivity') - idx, array = np_utils.interlaced_to_indexed(n_elem, connec[1]) - - PT.new_DataArray('ElementStartOffset', value=idx, parent=node) - connec[1] = array - -def create_mixed_elts_eso(node): - from cmaia.utils import layouts - ec = PT.get_node_from_name(node, 'ElementConnectivity')[1] - eso = np.empty(PT.Element.Size(node)+1, ec.dtype) - layouts.create_mixed_elts_eso(ec, eso) - PT.new_DataArray('ElementStartOffset', eso, parent=node) - -def enforce_ngon_pe_local(t): - """ - Shift the ParentElements values in order to make it start at 1, as requested by legacy tools. - - The tree is modified in place. - - Args: - t (CGNSTree(s)): Tree (or sequences of) starting at Zone_t level or higher. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #enforce_ngon_pe_local@start - :end-before: #enforce_ngon_pe_local@end - :dedent: 2 - - """ - for zone in zones_iterator(t): - try: - ngon_node = PT.Zone.NGonNode(zone) - except RuntimeError: #If no NGon, go to next zone - continue - pe = PT.get_child_from_name(ngon_node, 'ParentElements') - pe[1] = maia.algo.indexing.get_ngon_pe_local(ngon_node) - -def poly_new_to_old(tree, full_onera_compatibility=True): - """ - Transform a tree with polyhedral unstructured connectivity with new CGNS 4.x conventions to old CGNS 3.x conventions. - - The tree is modified in place. - - Args: - tree (CGNSTree): Tree described with new CGNS convention. - full_onera_compatibility (bool): if ``True``, shift NFace and ParentElements ids to begin at 1, irrespective of the NGon and NFace ElementRanges, and make the NFace connectivity unsigned - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #poly_new_to_old@start - :end-before: #poly_new_to_old@end - :dedent: 2 - """ - cg_version_node = PT.get_child_from_label(tree, 'CGNSLibraryVersion_t') - PT.set_value(cg_version_node, 3.1) - for z in PT.get_all_Zone_t(tree): - if PT.Zone.Type(z) != 'Unstructured': - continue - elif PT.Zone.has_ngon_elements(z): - - has_nface = PT.Zone.has_nface_elements(z) - - ngon = maia.pytree.Zone.NGonNode (z) - ngon_range = PT.get_value(PT.get_child_from_name(ngon , "ElementRange" )) - if has_nface: - nface = maia.pytree.Zone.NFaceNode(z) - nface_range = PT.get_value(PT.get_child_from_name(nface, "ElementRange" )) - nface_connec = PT.get_value(PT.get_child_from_name(nface, "ElementConnectivity")) - - if full_onera_compatibility: - # 1. shift ParentElements to 1 - pe_node = PT.get_child_from_name(ngon,"ParentElements") - if pe_node: - # pe = PT.get_value(pe_node) - # pe += (-nface_range[0]+1)*(pe>0) - pe_node[1] = maia.algo.indexing.get_ngon_pe_local(ngon) - - if has_nface: - # 2. do not use a signed NFace connectivity - np.absolute(nface_connec,out=nface_connec) - - # 3. shift NFace connectivity to 1 - nface_connec += -ngon_range[0]+1 - - # 4. indexed to interleaved - indexed_to_interleaved_connectivity(ngon) - if has_nface: - indexed_to_interleaved_connectivity(nface) - - else: # No NGon / NFace, but we may have to deal with MIXED elements - for elt in PT.iter_children_from_predicate(z, lambda n : PT.get_label(n) == 'Elements_t' - and PT.Element.CGNSName(n) == 'MIXED'): - PT.rm_children_from_name(elt, 'ElementStartOffset') - - - -def poly_old_to_new(tree): - """ - Transform a tree with polyhedral unstructured connectivity with old CGNS 3.x conventions to new CGNS 4.x conventions. - - The tree is modified in place. - - This function accepts trees with old ONERA conventions where NFace and ParentElements ids begin at 1, irrespective of the NGon and NFace ElementRanges, and where the NFace connectivity is unsigned. The resulting tree has the correct CGNS/SIDS conventions. - - Args: - tree (CGNSTree): Tree described with old CGNS convention. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #poly_old_to_new@start - :end-before: #poly_old_to_new@end - :dedent: 2 - """ - cg_version_node = PT.get_child_from_label(tree, 'CGNSLibraryVersion_t') - PT.set_value(cg_version_node, 4.2) - for z in PT.get_all_Zone_t(tree): - if PT.Zone.Type(z) != 'Unstructured': - continue - elif PT.Zone.has_ngon_elements(z): - has_nface = PT.Zone.has_nface_elements(z) - ngon = maia.pytree.Zone.NGonNode (z) - ngon_range = PT.get_value(PT.get_child_from_name(ngon , "ElementRange")) - if has_nface: - nface = maia.pytree.Zone.NFaceNode(z) - nface_range = PT.get_value(PT.get_child_from_name(nface, "ElementRange")) - - # 1. interleaved to indexed - interlaced_to_indexed_connectivity(ngon) - - # 2. shift ParentElements if necessary - pe_node = PT.get_child_from_name(ngon,"ParentElements") - if pe_node: - if not has_nface: #Induce NFace range for PE reconstruction - nface_range = [ngon_range[1]+1, ngon_range[1]+PT.Zone.n_cell(z)] - pe = PT.get_value(pe_node) - pe_no_0 = pe[pe>0] - min_pe = np.min(pe_no_0) - max_pe = np.max(pe_no_0) - if not (min_pe==nface_range[0] and max_pe==nface_range[1]): - if min_pe!=1: - raise RuntimeError("ParentElements values are not SIDS-compliant, and they do not start at 1") - else: - pe += (+nface_range[0]-1)*(pe>0) - - # 3. NFace - if has_nface: - nface_connec = PT.get_value(PT.get_child_from_name(nface, "ElementConnectivity")) - n_cell = nface_range[1] - nface_range[0] - if np.min(nface_connec)<0 or n_cell==1: # NFace is signed (if only one cell, it is signed despite being positive) - # 3.1. interleaved to indexed - interlaced_to_indexed_connectivity(nface) - nface_connec = PT.get_value(PT.get_child_from_name(nface, "ElementConnectivity")) - - # 3.2. shift - sign_nf = np.sign(nface_connec) - abs_nf = np.absolute(nface_connec) - min_nf = np.min(abs_nf) - max_nf = np.max(abs_nf) - if not (min_nf==ngon_range[0] and max_nf==ngon_range[1]): - if min_nf!=1: - raise RuntimeError("NFace ElementConnectivity values are not SIDS-compliant, and they do not start at 1") - else: - abs_nf += +ngon_range[0]-1 - nface_connec[:] = abs_nf * sign_nf - else: # NFace is not signed: need to recompute it - PT.rm_child(z,nface) - if not pe_node: - raise RuntimeError("NFace is not signed: this is not compliant. However, a ParentElements is needed to recompute a correct NFace") - if ngon_range[0] != 1: - raise NotImplementedError("NFace is not signed: this is not compliant. It needs to be recomputed, but not implemented in case NGon is not first") - maia.algo.pe_to_nface(z) - - else: # No NGon / NFace, but we may have to deal with MIXED elements - for elt in PT.iter_children_from_predicate(z, lambda n : PT.get_label(n) == 'Elements_t' - and PT.Element.CGNSName(n) == 'MIXED'): - create_mixed_elts_eso(elt) diff --git a/maia/algo/seq/test/test_compatibility.py b/maia/algo/seq/test/test_compatibility.py deleted file mode 100644 index 8370fa7e..00000000 --- a/maia/algo/seq/test/test_compatibility.py +++ /dev/null @@ -1,186 +0,0 @@ -import pytest - -import numpy as np -import os -from mpi4py import MPI - -from maia.utils import test_utils as TU -from maia.pytree.yaml import parse_yaml_cgns - -import cmaia -import maia -import maia.pytree as PT - - -@pytest.fixture -def poly_tree_new(): - filename = os.path.join(TU.mesh_dir,'hex_2_prism_2.yaml') - t = maia.io.file_to_dist_tree(filename,MPI.COMM_SELF) - maia.algo.dist.elements_to_ngons(t,MPI.COMM_SELF) - maia.io.distribution_tree.clean_distribution_info(t) # remove distribution info to make it a regular pytree - return t - -@pytest.mark.skipif(not cmaia.cpp20_enabled, reason="Require ENABLE_CPP20 compilation flag") -def test_enfore_ngon_pe_local(poly_tree_new): - t = poly_tree_new - maia.algo.seq.enforce_ngon_pe_local(t) - - ngon = PT.get_node_from_name(t,"NGON_n") - #Those two are not modified - assert PT.get_value(PT.get_child_from_name(ngon,"ElementStartOffset")).size == 19 - assert PT.get_value(PT.get_child_from_name(ngon,"ElementConnectivity")).size == 69 - pe = PT.get_value(PT.get_child_from_name(ngon,"ParentElements")) - - assert (pe == np.array([[1,0],[2,0],[3,0],[4,0],[1,0],[3,0],[2,0],[4,0],[1,0], - [2,0],[1,0],[2,0],[3,0],[4,0],[3,4],[1,3],[1,2],[2,4]])).all() - -@pytest.mark.skipif(not cmaia.cpp20_enabled, reason="Require ENABLE_CPP20 compilation flag") -def test_poly_new_to_old(poly_tree_new): - t = poly_tree_new - maia.algo.seq.poly_new_to_old(t) - - ngon = PT.get_node_from_name(t,"NGON_n") - nface = PT.get_node_from_name(t,"NFACE_n") - ngon_ec = PT.get_value(PT.get_child_from_name(ngon,"ElementConnectivity")) - pe = PT.get_value(PT.get_child_from_name(ngon,"ParentElements")) - nface_ec = PT.get_value(PT.get_child_from_name(nface,"ElementConnectivity")) - - assert (ngon_ec == np.array([ 4, 1,6, 9,4, 4, 6 ,11,14, 9, 4, 3, 5,10, 8, 4, 8,10,15,13, - 4, 1,2, 7,6, 4, 2 , 3, 8, 7, 4, 6, 7,12,11, 4, 7, 8,13,12, - 4, 4,9,10,5, 4, 9 ,14,15,10, 4, 1, 4, 5, 2, 4, 11,12,15,14, - 3, 2,5,3 , 3, 12,13,15 , 3, 7, 8,10, - 4, 2,5,10,7, 4, 6 , 7,10, 9, 4, 7,10,15,12] )).all() - assert (pe == np.array([[1,0],[2,0],[3,0],[4,0],[1,0],[3,0],[2,0],[4,0],[1,0], - [2,0],[1,0],[2,0],[3,0],[4,0],[3,4],[1,3],[1,2],[2,4]])).all() - assert (nface_ec == np.array([ 6, 11,5,16,9,1,17, 6, 17,7,18,10,2,12, 5, 6,3,16,13,15, 5, 8,4,18,15,14])).all() - - -@pytest.mark.skipif(not cmaia.cpp20_enabled, reason="Require ENABLE_CPP20 compilation flag") -def test_poly_new_to_old_only_interleave(poly_tree_new): - t = poly_tree_new - maia.algo.seq.poly_new_to_old(t,full_onera_compatibility=False) - - ngon = PT.get_node_from_name(t,"NGON_n") - nface = PT.get_node_from_name(t,"NFACE_n") - ngon_ec = PT.get_value(PT.get_child_from_name(ngon,"ElementConnectivity")) - pe = PT.get_value(PT.get_child_from_name(ngon,"ParentElements")) - nface_ec = PT.get_value(PT.get_child_from_name(nface,"ElementConnectivity")) - - assert (ngon_ec == np.array([ 4, 1,6, 9,4, 4, 6 ,11,14, 9, 4, 3, 5,10, 8, 4, 8,10,15,13, - 4, 1,2, 7,6, 4, 2 , 3, 8, 7, 4, 6, 7,12,11, 4, 7, 8,13,12, - 4, 4,9,10,5, 4, 9 ,14,15,10, 4, 1, 4, 5, 2, 4, 11,12,15,14, - 3, 2,5,3 , 3, 12,13,15 , 3, 7, 8,10, - 4, 2,5,10,7, 4, 6 , 7,10, 9, 4, 7,10,15,12] )).all() - assert (pe == np.array([[19,0],[20,0],[21,0],[22,0],[19,0],[21, 0],[20, 0],[22, 0],[19, 0], - [20,0],[19,0],[20,0],[21,0],[22,0],[21,22],[19,21],[19,20],[20,22]])).all() - assert (nface_ec == np.array([ 6, 11,5,16,9,1,17, 6, -17,7,18,10,2,12, 5, 6,3,-16,13,15, 5, 8,4,-18,-15,14])).all() - -def test_poly_new_to_old_2d(): - tree = maia.factory.generate_dist_block(3, "TRI_3", MPI.COMM_SELF) - maia.algo.dist.convert_elements_to_ngon(tree, MPI.COMM_SELF) - maia.algo.seq.poly_new_to_old(tree) - ngon = PT.get_node_from_name(tree, "NGonElements") - ngon_ec = PT.get_value(PT.get_child_from_name(ngon,"ElementConnectivity")) - assert (ngon_ec == np.array([3, 1,2,4, 3, 4,2,5, 3, 5,2,3, 3, 5,3,6, 3, 4,5,7, 3, 7,5,8, 3, 8,5,6, 3, 8,6,9])).all() - -@pytest.mark.skipif(not cmaia.cpp20_enabled, reason="Require ENABLE_CPP20 compilation flag") -def test_poly_old_to_new(poly_tree_new): - t = poly_tree_new - maia.algo.seq.poly_new_to_old(t) # Note: we are not testing that! - - maia.algo.seq.poly_old_to_new(t) - ngon = PT.get_node_from_name(t,"NGON_n") - nface = PT.get_node_from_name(t,"NFaceElements") - ngon_ec = PT.get_value(PT.get_child_from_name(ngon,"ElementConnectivity")) - ngon_eso = PT.get_value(PT.get_child_from_name(ngon,"ElementStartOffset")) - pe = PT.get_value(PT.get_child_from_name(ngon,"ParentElements")) - nface_ec = PT.get_value(PT.get_child_from_name(nface,"ElementConnectivity")) - nface_eso = PT.get_value(PT.get_child_from_name(nface,"ElementStartOffset")) - - assert (ngon_eso == np.array([0,4,8,12,16,20,24,28,32,36,40,44,48,51,54,57,61,65,69])).all() - assert (ngon_ec == np.array([ 1,6, 9,4, 6 ,11,14, 9, 3, 5,10, 8, 8,10,15,13, - 1,2, 7,6, 2 , 3, 8, 7, 6, 7,12,11, 7, 8,13,12, - 4,9,10,5, 9 ,14,15,10, 1, 4, 5, 2, 11,12,15,14, - 2,5,3 , 12,13,15 , 7, 8,10, - 2,5,10,7, 6 , 7,10, 9, 7,10,15,12] )).all() - assert (pe == np.array([[19,0],[20,0],[21,0],[22,0],[19,0],[21, 0],[20, 0],[22, 0],[19, 0], - [20,0],[19,0],[20,0],[21,0],[22,0],[21,22],[19,21],[19,20],[20,22]])).all() - assert (nface_eso == np.array([0,6,12,17,22])).all() - assert (nface_ec == np.array([1,5,9,11,16,17, 2,7,10,12,18,-17, 3,6,13,15,-16, 4,8,14,-15,-18])).all() - - -@pytest.mark.skipif(not cmaia.cpp20_enabled, reason="Require ENABLE_CPP20 compilation flag") -def test_poly_old_to_new_only_index(poly_tree_new): - t = poly_tree_new - maia.algo.seq.poly_new_to_old(t,full_onera_compatibility=False) # Note: we are not testing that! - - maia.algo.seq.poly_old_to_new(t) - ngon = PT.get_node_from_name(t,"NGON_n") - nface = PT.get_node_from_name(t,"NFACE_n") - ngon_ec = PT.get_value(PT.get_child_from_name(ngon,"ElementConnectivity")) - ngon_eso = PT.get_value(PT.get_child_from_name(ngon,"ElementStartOffset")) - pe = PT.get_value(PT.get_child_from_name(ngon,"ParentElements")) - nface_ec = PT.get_value(PT.get_child_from_name(nface,"ElementConnectivity")) - nface_eso = PT.get_value(PT.get_child_from_name(nface,"ElementStartOffset")) - - assert (ngon_eso == np.array([0,4,8,12,16,20,24,28,32,36,40,44,48,51,54,57,61,65,69])).all() - assert (ngon_ec == np.array([ 1,6, 9,4, 6 ,11,14, 9, 3, 5,10, 8, 8,10,15,13, - 1,2, 7,6, 2 , 3, 8, 7, 6, 7,12,11, 7, 8,13,12, - 4,9,10,5, 9 ,14,15,10, 1, 4, 5, 2, 11,12,15,14, - 2,5,3 , 12,13,15 , 7, 8,10, - 2,5,10,7, 6 , 7,10, 9, 7,10,15,12] )).all() - assert (pe == np.array([[19,0],[20,0],[21,0],[22,0],[19,0],[21, 0],[20, 0],[22, 0],[19, 0], - [20,0],[19,0],[20,0],[21,0],[22,0],[21,22],[19,21],[19,20],[20,22]])).all() - assert (nface_eso == np.array([0,6,12,17,22])).all() - assert (nface_ec == np.array([11,5,16,9,1,17, -17,7,18,10,2,12, 6,3,-16,13,15, 8,4,-18,-15,14])).all() - -@pytest.mark.skipif(not cmaia.cpp20_enabled, reason="Require ENABLE_CPP20 compilation flag") -def test_poly_old_to_new_no_nface(poly_tree_new): - t = poly_tree_new - maia.algo.seq.poly_new_to_old(t) # Note: we are not testing that! - maia.pytree.rm_nodes_from_name(t,"NFACE_n") - - maia.algo.seq.poly_old_to_new(t) - ngon = PT.get_node_from_name(t,"NGON_n") - ngon_eso = PT.get_value(PT.get_child_from_name(ngon,"ElementStartOffset")) - ngon_ec = PT.get_value(PT.get_child_from_name(ngon,"ElementConnectivity")) - ngon_pe = PT.get_value(PT.get_child_from_name(ngon,"ParentElements")) - assert (ngon_eso == np.array([0,4,8,12,16,20,24,28,32,36,40,44,48,51,54,57,61,65,69])).all() - assert (ngon_ec == np.array([ 1,6, 9,4, 6 ,11,14, 9, 3, 5,10, 8, 8,10,15,13, - 1,2, 7,6, 2 , 3, 8, 7, 6, 7,12,11, 7, 8,13,12, - 4,9,10,5, 9 ,14,15,10, 1, 4, 5, 2, 11,12,15,14, - 2,5,3 , 12,13,15 , 7, 8,10, - 2,5,10,7, 6 , 7,10, 9, 7,10,15,12] )).all() - assert (ngon_pe == np.array([[19,0],[20,0],[21,0],[22,0],[19,0],[21, 0],[20, 0],[22, 0],[19, 0], - [20,0],[19,0],[20,0],[21,0],[22,0],[21,22],[19,21],[19,20],[20,22]])).all() - - -@pytest.mark.skipif(not cmaia.cpp20_enabled, reason="Require ENABLE_CPP20 compilation flag") -def test_poly_old_to_new_no_pe(poly_tree_new): - t = poly_tree_new - maia.algo.seq.poly_new_to_old(t) # Note: we are not testing that! - maia.pytree.rm_nodes_from_name(t,"ParentElements") - - with pytest.raises(RuntimeError): - maia.algo.seq.poly_old_to_new(t) - -def test_mixed_new_to_old(): - mesh_file = os.path.join(TU.sample_mesh_dir, 'mixed_with_bc.yaml') - tree = maia.io.read_tree(mesh_file) - - ec_bck = PT.get_node_from_name(tree, 'ElementConnectivity')[1].copy() - maia.algo.seq.poly_new_to_old(tree) - assert np.array_equal(ec_bck, PT.get_node_from_name(tree, 'ElementConnectivity')[1]) - assert PT.get_node_from_name(tree, 'ElementStartOffset') is None - -def test_mixed_old_to_new(): - mesh_file = os.path.join(TU.sample_mesh_dir, 'mixed_with_bc.yaml') - tree_bck = maia.io.read_tree(mesh_file) - - # Setup test (tested in mixed_new_to_old) - tree = PT.deep_copy(tree_bck) - maia.algo.seq.poly_new_to_old(tree) - - maia.algo.seq.poly_old_to_new(tree) - assert PT.is_same_tree(tree, tree_bck) - diff --git a/maia/algo/test/test_apply_function_to_nodes.py b/maia/algo/test/test_apply_function_to_nodes.py deleted file mode 100644 index 21ef4c06..00000000 --- a/maia/algo/test/test_apply_function_to_nodes.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest - -import maia.pytree as PT - -from maia.pytree.yaml import parse_yaml_cgns -from maia.algo.apply_function_to_nodes import apply_to_zones, zones_iterator - -def test_apply_to_zones(): - - def add_vtx(zone): - zone[1][0][0] += 1 - - yt = """ - BaseA CGNSBase_t: - zoneI Zone_t I4 [[8,5,0]]: - ZoneType ZoneType_t "Unstructured": - BaseB CGNSBase_t: - zoneII Zone_t I4 [[27,40,0]]: - ZoneType ZoneType_t "Unstructured": - ZoneBC ZoneBC_t: - """ - tree = parse_yaml_cgns.to_cgns_tree(yt) - zoneI = PT.get_node_from_name(tree, 'zoneI') - zoneII = PT.get_node_from_name(tree, 'zoneII') - other_base = PT.get_node_from_name(tree, 'BaseB') - - # Single zone - apply_to_zones(add_vtx, zoneI) - assert PT.Zone.n_vtx(zoneI) == 9 - #Base - apply_to_zones(add_vtx, other_base) - assert PT.Zone.n_vtx(zoneII) == 28 - #Tree - apply_to_zones(add_vtx, tree) - assert PT.Zone.n_vtx(zoneI) == 10 - assert PT.Zone.n_vtx(zoneII) == 29 - - #BC (err) - with pytest.raises(Exception): - apply_to_zones(add_vtx, PT.get_node_from_label(tree, 'ZoneBC_t')) - - -def test_zones_iterator(): - yt = """ - BaseA CGNSBase_t: - zoneI Zone_t: - BaseB CGNSBase_t: - zoneII Zone_t: - ZoneBC ZoneBC_t: - """ - - tree = parse_yaml_cgns.to_cgns_tree(yt) - zone = PT.get_node_from_name(tree, 'zoneI') - other_base = PT.get_node_from_name(tree, 'BaseB') - - # Single zone - zone = PT.get_all_Zone_t(tree)[0] - assert [z[0] for z in zones_iterator(zone)] == ['zoneI'] - assert [z[0] for z in zones_iterator(other_base)] == ['zoneII'] - assert [z[0] for z in zones_iterator(tree)] == ['zoneI', 'zoneII'] - - #Other node (err) - with pytest.raises(ValueError): - for z in zones_iterator(PT.get_node_from_label(tree, 'ZoneBC_t')): - pass - - diff --git a/maia/algo/test/test_indexing.py b/maia/algo/test/test_indexing.py deleted file mode 100644 index 26f0db3e..00000000 --- a/maia/algo/test/test_indexing.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -from maia.pytree.yaml import parse_yaml_cgns - -from maia.algo import indexing - -def test_get_ngon_pe_local(): - yt = """ - NGonElements Elements_t [22, 0]: - ElementRange IndexRange_t [1, 8]: - ParentElements DataArray_t [[9, 0], [10, 0], [11, 12], [0, 12]]: - """ - ngon = parse_yaml_cgns.to_node(yt) - assert (indexing.get_ngon_pe_local(ngon) == np.array([[9-8,0], [10-8,0], [11-8,12-8], [0,12-8]])).all() - - yt = """ - NGonElements Elements_t [22, 0]: - ElementRange IndexRange_t [1, 8]: - ParentElements DataArray_t [[1, 0], [2, 0], [3, 4], [0, 4]]: - """ - ngon = parse_yaml_cgns.to_node(yt) - assert (indexing.get_ngon_pe_local(ngon) == np.array([[1,0], [2,0], [3,4], [0,4]])).all() - - yt = """ - NGonElements Elements_t [22, 0]: - ElementRange IndexRange_t [1, 8]: - """ - ngon = parse_yaml_cgns.to_node(yt) - with pytest.raises(RuntimeError): - indexing.get_ngon_pe_local(ngon) - diff --git a/maia/algo/test/test_transform.py b/maia/algo/test/test_transform.py deleted file mode 100644 index 9c292e97..00000000 --- a/maia/algo/test/test_transform.py +++ /dev/null @@ -1,159 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory.dcube_generator import dcube_generate - -from maia.algo import transform - -from maia.utils import logging as mlog - -class log_capture: - def __init__(self): - self.logs = '' - def log(self, msg): - self.logs += msg - -def test_transformation_zone_void(): - yz = """ - Zone Zone_t I4 [[18,4,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t: - R4 : [ 0,1,2, - 0,1,2, - 0,1,2, - 0,1,2, - 0,1,2, - 0,1,2 ] - CoordinateY DataArray_t: - R4 : [ 0,0,0, - 1,1,1, - 2,2,2, - 0,0,0, - 1,1,1, - 2,2,2 ] - CoordinateZ DataArray_t: - R4 : [ 0,0,0, - 0,0,0, - 0,0,0, - 1,1,1, - 1,1,1, - 1,1,1 ] - """ - zone = parse_yaml_cgns.to_node(yz) - zone_bck = PT.deep_copy(zone) - transform.transform_affine(zone) - assert PT.is_same_tree(zone_bck, zone) - -@pytest_parallel.mark.parallel(1) -def test_transform_affine(comm): - - def check_vect_field(old_node, new_node, field_name): - old_data = [PT.get_node_from_name(old_node, f"{field_name}{c}")[1] for c in ['X', 'Y', 'Z']] - new_data = [PT.get_node_from_name(new_node, f"{field_name}{c}")[1] for c in ['X', 'Y', 'Z']] - assert np.allclose(old_data[0], -new_data[0]) - assert np.allclose(old_data[1], -new_data[1]) - assert np.allclose(old_data[2], new_data[2]) - def check_scal_field(old_node, new_node, field_name): - old_data = PT.get_node_from_name(old_node, field_name)[1] - new_data = PT.get_node_from_name(new_node, field_name)[1] - assert (old_data == new_data).all() - - dist_tree = dcube_generate(4, 1., [0., -.5, -.5], comm) - dist_zone = PT.get_all_Zone_t(dist_tree)[0] - - # Initialise some fields - cell_distri = MT.getDistribution(dist_zone, 'Cell')[1] - n_cell_loc = cell_distri[1] - cell_distri[0] - fs = PT.new_FlowSolution('FlowSolution', loc='CellCenter', parent=dist_zone) - PT.new_DataArray('scalar', np.random.random(n_cell_loc), parent=fs) - PT.new_DataArray('fieldX', np.random.random(n_cell_loc), parent=fs) - PT.new_DataArray('fieldY', np.random.random(n_cell_loc), parent=fs) - PT.new_DataArray('fieldZ', np.random.random(n_cell_loc), parent=fs) - - dist_zone_ini = PT.deep_copy(dist_zone) - transform.transform_affine(dist_zone, rotation_angle=np.array([0.,0.,np.pi]), apply_to_fields=True) - - check_vect_field(dist_zone_ini, dist_zone, "Coordinate") - check_vect_field(dist_zone_ini, dist_zone, "field") - check_scal_field(dist_zone_ini, dist_zone, "scalar") - -@pytest_parallel.mark.parallel(1) -def test_transform_affine_2d(comm): - - dist_tree = maia.factory.generate_dist_block(4, 'S', comm, origin=[.0, .0]) - dist_zone = PT.get_all_Zone_t(dist_tree)[0] - - # Initialise some fields - cell_distri = MT.getDistribution(dist_zone, 'Cell')[1] - n_cell_loc = cell_distri[1] - cell_distri[0] - fs = PT.new_FlowSolution('FlowSolution', loc='CellCenter', parent=dist_zone) - PT.new_DataArray('scalar', np.random.random(n_cell_loc), parent=fs) - PT.new_DataArray('fieldX', np.random.random(n_cell_loc), parent=fs) - PT.new_DataArray('fieldY', np.random.random(n_cell_loc), parent=fs) - - dist_zone_ini = PT.deep_copy(dist_zone) - transform.transform_affine(dist_zone, rotation_center=np.zeros(2), translation=np.zeros(2), rotation_angle=np.pi, apply_to_fields=True) - assert np.allclose(PT.get_node_from_name(dist_zone_ini, 'scalar')[1], - PT.get_node_from_name(dist_zone, 'scalar')[1]) - assert np.allclose( PT.get_node_from_name(dist_zone_ini, 'fieldX')[1], - -1*PT.get_node_from_name(dist_zone, 'fieldX')[1]) - assert np.allclose( PT.get_node_from_name(dist_zone_ini, 'fieldY')[1], - -1*PT.get_node_from_name(dist_zone, 'fieldY')[1]) - -@pytest_parallel.mark.parallel(2) -def test_transform_affine_s_part(comm): - dist_tree = maia.factory.generate_dist_block(4, 'S', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - part_tree_bck = PT.deep_copy(part_tree) - transform.transform_affine(part_tree, translation=[5,1,2]) - assert (PT.get_node_from_name(part_tree, 'CoordinateX')[1] == 5+PT.get_node_from_name(part_tree_bck, 'CoordinateX')[1]).all() - assert (PT.get_node_from_name(part_tree, 'CoordinateY')[1] == 1+PT.get_node_from_name(part_tree_bck, 'CoordinateY')[1]).all() - assert (PT.get_node_from_name(part_tree, 'CoordinateZ')[1] == 2+PT.get_node_from_name(part_tree_bck, 'CoordinateZ')[1]).all() - - dist_tree = maia.factory.generate_dist_block([4,4], 'S', comm, origin=[0,0]) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - part_zone = PT.get_node_from_label(part_tree, 'Zone_t') - fs = PT.new_FlowSolution('FlowSolution', loc='CellCenter', parent=part_zone) - PT.new_DataArray('fieldX', np.random.random(PT.Zone.n_cell(part_zone)), parent=fs) - PT.new_DataArray('fieldY', np.random.random(PT.Zone.n_cell(part_zone)), parent=fs) - part_tree_bck = PT.deep_copy(part_tree) - transform.transform_affine(part_tree, rotation_center=np.zeros(2), translation=np.zeros(2), rotation_angle=0.5*np.pi, apply_to_fields=True) - assert np.allclose(PT.get_node_from_name(part_tree, 'fieldX')[1], -PT.get_node_from_name(part_tree_bck, 'fieldY')[1]) - assert np.allclose(PT.get_node_from_name(part_tree, 'fieldY')[1], PT.get_node_from_name(part_tree_bck, 'fieldX')[1]) - -@pytest_parallel.mark.parallel(1) -def test_scale_mesh(comm): - # To check if warning displays - log_collector = log_capture() - mlog.add_printer_to_logger('maia-warnings', log_collector) - - dist_tree = maia.factory.generate_dist_block(4, 'Poly', comm) - dist_zone = PT.get_all_Zone_t(dist_tree)[0] - - cx_bck = PT.get_node_from_name(dist_zone, 'CoordinateX')[1].copy() - cy_bck = PT.get_node_from_name(dist_zone, 'CoordinateY')[1].copy() - cz_bck = PT.get_node_from_name(dist_zone, 'CoordinateZ')[1].copy() - - transform.scale_mesh(dist_tree, [1.0, 2.0, 0.5]) - assert (PT.get_node_from_name(dist_tree, 'CoordinateX')[1] == cx_bck).all() - assert (PT.get_node_from_name(dist_tree, 'CoordinateY')[1] == 2*cy_bck).all() - assert (PT.get_node_from_name(dist_tree, 'CoordinateZ')[1] == 0.5*cz_bck).all() - - assert log_collector.logs == '' - - dist_tree = maia.factory.generate_dist_block([4,4], 'S', comm, origin=np.zeros(2)) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - PT.new_FlowSolution(loc='CellCenter', fields={'Field': np.ones(PT.Zone.n_cell(zone))}, parent=zone) - dist_tree_bck = PT.deep_copy(dist_tree) - transform.scale_mesh(dist_tree, 5) - assert np.allclose(PT.get_node_from_name(dist_tree, 'CoordinateX')[1], 5*PT.get_node_from_name(dist_tree_bck, 'CoordinateX')[1]) - assert np.allclose(PT.get_node_from_name(dist_tree, 'CoordinateY')[1], 5*PT.get_node_from_name(dist_tree_bck, 'CoordinateY')[1]) - - assert "Scaling mesh does not affect fields, and some are present in tree." in log_collector.logs diff --git a/maia/algo/transform.py b/maia/algo/transform.py deleted file mode 100644 index b876c5ad..00000000 --- a/maia/algo/transform.py +++ /dev/null @@ -1,149 +0,0 @@ -import numpy as np - -import maia.pytree as PT -from maia.utils import py_utils, np_utils -from maia.algo.apply_function_to_nodes import zones_iterator - -from maia.utils import logging as mlog - -def transform_affine_zone(zone, - vtx_mask, - rotation_center, - rotation_angle, - translation, - apply_to_fields): - """ - Implementation of transform affine (see associated documentation) for - a given zone. - - In addition, this function takes a bool array of shaped as coords array and - apply the periodicity only to the vertices evaluating to True. - """ - # Transform coords - for grid_co in PT.iter_children_from_label(zone, "GridCoordinates_t"): - coords_n = [PT.get_child_from_name(grid_co, f"Coordinate{c}") for c in ['X', 'Y', 'Z']] - phy_dim = 2 if coords_n[2] is None else 3 - coords_n = coords_n[:phy_dim] - coords = [PT.get_value(n)[vtx_mask] for n in coords_n] - - if phy_dim == 3: - tr_coords = np_utils.transform_cart_vectors(*coords, translation, rotation_center, rotation_angle) - else: - tr_coords = np_utils.transform_cart_vectors_2d(*coords, translation, rotation_center, rotation_angle) - for coord_n, tr_coord in zip(coords_n, tr_coords): - coord_n[1][vtx_mask] = tr_coord - - # Transform fields - if apply_to_fields: - fields_nodes = PT.get_children_from_label(zone, "FlowSolution_t") - fields_nodes += PT.get_children_from_label(zone, "DiscreteData_t") - fields_nodes += PT.get_children_from_label(zone, "ZoneSubRegion_t") - for bc in PT.iter_children_from_predicates(zone, "ZoneBC_t/BC_t"): - fields_nodes += PT.get_children_from_label(bc, "BCDataSet_t") - for fields_node in fields_nodes: - is_full_vtx = PT.Subset.GridLocation(fields_node) == 'Vertex' and \ - PT.get_label(fields_node) in ['FlowSolution_t', 'DiscreteData_t'] and \ - PT.get_child_from_name(fields_node, 'PointList') is None and \ - PT.get_child_from_name(fields_node, 'PointRange') is None - data_names = [PT.get_name(data) for data in PT.iter_nodes_from_label(fields_node, "DataArray_t")] - cartesian_vectors_basenames = py_utils.find_cartesian_vector_names(data_names, phy_dim) - for basename in cartesian_vectors_basenames: - vectors_n = [PT.get_node_from_name_and_label(fields_node, f"{basename}{c}", 'DataArray_t') for c in ['X', 'Y', 'Z'][:phy_dim]] - if is_full_vtx: - vectors = [PT.get_value(n)[vtx_mask] for n in vectors_n] - else: - vectors = [PT.get_value(n) for n in vectors_n] - # Assume that vectors are position independant - # Be careful, if coordinates vector needs to be transform, the translation is not applied ! - if phy_dim == 3: - tr_vectors = np_utils.transform_cart_vectors(*vectors, rotation_center=rotation_center, rotation_angle=rotation_angle) - else: - tr_vectors = np_utils.transform_cart_vectors_2d(*vectors, rotation_center=rotation_center, rotation_angle=rotation_angle) - for vector_n, tr_vector in zip(vectors_n, tr_vectors): - if is_full_vtx: - vector_n[1][vtx_mask] = tr_vector - else: - vector_n[1] = tr_vector - - - -def transform_affine(t, - rotation_center = np.zeros(3), - rotation_angle = np.zeros(3), - translation = np.zeros(3), - apply_to_fields = False): - """Apply the affine transformation to the coordinates of the given zone. - - Input zone(s) can be either structured or unstructured, but must have cartesian coordinates. - Transformation is defined by - - .. math:: - \\tilde v = R \\cdot (v - c) + c + t - - where c, t are the rotation center and translation vectors and R is the rotation matrix. - Note that when the physical dimension of the mesh is set to 2, rotation_angle must - be a scalar float. - - Input tree is modified inplace. - - Args: - t (CGNSTree(s)): Tree (or sequences of) starting at Zone_t level or higher. - rotation_center (array): center coordinates of the rotation - rotation_angler (array): angles of the rotation - translation (array): translation vector components - apply_to_fields (bool, optional) : - if True, apply the rotation vector to the vectorial fields found under - following nodes : ``FlowSolution_t``, ``DiscreteData_t``, ``ZoneSubRegion_t``, ``BCDataset_t``. - Defaults to False. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: #transform_affine@start - :end-before: #transform_affine@end - :dedent: 2 - """ - for zone in zones_iterator(t): - any_coord = PT.get_node_from_predicates(zone, 'GridCoordinates_t/DataArray_t') - # Don't use PT.Zone.VertexSize because it won't work on dist_tree - vtx_mask = np.ones(PT.get_value(any_coord).shape, bool) - transform_affine_zone(zone, vtx_mask, rotation_center, rotation_angle, translation, apply_to_fields) - -def scale_mesh(t, s=1.): - """Rescale the GridCoordinates of the input mesh. - - Input zone(s) can be either structured or unstructured, but must have cartesian coordinates. - Transformation is defined by - - .. math:: - \\tilde v = S \\cdot v - - where S is the scaling matrix. - Input tree is modified inplace. - - Args: - t (CGNSTree(s)): Tree (or sequences of) starting at Zone_t level or higher - s (float or array of float): Scaling factor in each physical dimension. Scalars automatically - extend to uniform array. - - Example: - .. literalinclude:: snippets/test_algo.py - :start-after: scale_mesh@start - :end-before: #scale_mesh@end - :dedent: 2 - """ - scaling = 3 * [s] if isinstance(s, (int, float)) else s - fields_found = False - is_container = lambda n: PT.get_label(n) in ['FlowSolution_t', 'DiscreteData_t', 'ZoneSubRegion_t'] - for zone in zones_iterator(t): - for grid_co in PT.get_children_from_label(zone, 'GridCoordinates_t'): - for idir, dir in enumerate(['X', 'Y', 'Z']): - node = PT.get_child_from_name(grid_co, f'Coordinate{dir}') - if node is not None: - node[1] *= scaling[idir] - - if PT.get_child_from_predicate(zone, is_container) is not None or \ - PT.get_child_from_predicates(zone, 'ZoneBC_t/BC_t/BCDataSet_t/BCData_t') is not None: - fields_found = True - - if fields_found: - mlog.warning(f"Scaling mesh does not affect fields, and some are present in tree. Update their value if needed.") \ No newline at end of file diff --git a/maia/algo/tree_algo.pybind.cpp b/maia/algo/tree_algo.pybind.cpp deleted file mode 100644 index 08e07349..00000000 --- a/maia/algo/tree_algo.pybind.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "maia/algo/tree_algo.pybind.hpp" -#if __cplusplus > 201703L -#include "cpp_cgns/interop/pycgns_converter.hpp" -#include "maia/utils/parallel/mpi4py.hpp" -#include "maia/algo/common/poly_algorithm.hpp" - -namespace py = pybind11; - -template auto -apply_cpp_cgns_function_to_pytree(F&& f) { - return [&f](py::list pt) { - cgns::tree t = cgns::to_cpp_tree(pt); - f(t); - update_py_tree(std::move(t),pt); - }; -} - -const auto indexed_to_interleaved_connectivity = apply_cpp_cgns_function_to_pytree(maia::indexed_to_interleaved_connectivity); -const auto interleaved_to_indexed_connectivity = apply_cpp_cgns_function_to_pytree(maia::interleaved_to_indexed_connectivity); - - -void register_tree_algo_module(py::module_& parent) { - - py::module_ m = parent.def_submodule("tree_algo"); - m.def("indexed_to_interleaved_connectivity" , indexed_to_interleaved_connectivity , "Turn NGon or NFace description with ElementStartOffset to old convention"); - m.def("interleaved_to_indexed_connectivity" , interleaved_to_indexed_connectivity , "Turn NGon or NFace description without ElementStartOffset to new convention"); - - -} -#else //C++==17 - -namespace py = pybind11; -void register_tree_algo_module(py::module_& parent) { - - py::module_ m = parent.def_submodule("tree_algo"); - -} -#endif //C++>17 diff --git a/maia/algo/tree_algo.pybind.hpp b/maia/algo/tree_algo.pybind.hpp deleted file mode 100644 index ecc6e8ea..00000000 --- a/maia/algo/tree_algo.pybind.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#include - -void register_tree_algo_module(pybind11::module_& parent); - diff --git a/maia/cmaia.pybind.cpp b/maia/cmaia.pybind.cpp deleted file mode 100644 index afa92fc2..00000000 --- a/maia/cmaia.pybind.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#include "maia/pytree/pytree.pybind.hpp" -#include "maia/utils/utils.pybind.hpp" -#include "maia/algo/tree_algo.pybind.hpp" -#include "maia/algo/dist/dist_algo.pybind.hpp" -#include "maia/algo/part/part_algo.pybind.hpp" - - -namespace py = pybind11; - -PYBIND11_MODULE(cmaia, m) { - - #if __cplusplus > 201703L - m.attr("cpp20_enabled") = py::bool_(1); - #else - m.attr("cpp20_enabled") = py::bool_(0); - #endif - - register_pytree_module(m); - register_utils_module(m); - - register_tree_algo_module(m); - register_dist_algo_module(m); - register_part_algo_module(m); - -} diff --git a/maia/conftest.py b/maia/conftest.py deleted file mode 100644 index a9eb4e1a..00000000 --- a/maia/conftest.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -import os -from mpi4py import MPI - - -# https://stackoverflow.com/questions/59577426/how-to-rename-the-title-of-the-html-report-generated-by-pytest-html-plug-in -@pytest.hookimpl(tryfirst=True) -def pytest_configure(config): - - comm = MPI.COMM_WORLD - if comm.Get_rank() == 0: - if not os.path.exists('reports'): - os.makedirs('reports') - if not os.path.exists('reports/assets'): - os.makedirs('reports/assets') - comm.barrier() - - #Only proc 0 holds test results, others are empty - if comm.Get_rank() == 0: - config.option.htmlpath = 'reports/' + "report_unit_test.html" - config.option.xmlpath = 'reports/' + "report_unit_test.xml" - - -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item, call): - - pytest_html = item.config.pluginmanager.getplugin('html') - outcome = yield - report = outcome.get_result() - extra = getattr(report, 'extra', []) - if report.when == 'call': - # always add url to report - # extra.append(pytest_html.extras.url('http://www.example.com/')) - # extra.append(pytest_html.extras.png("/stck/bmaugars/tmp/export.png")) - xfail = hasattr(report, 'wasxfail') - if (report.skipped and xfail) or (report.failed and not xfail): - # only add additional html on failure - extra.append(pytest_html.extras.html('
Additional HTML
')) - report.extra = extra - diff --git a/maia/factory/__init__.py b/maia/factory/__init__.py deleted file mode 100644 index ea87dbce..00000000 --- a/maia/factory/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .dcloud_generator import generate_dist_points - -from .dcube_generator import generate_dist_block - -from .dist_from_part import recover_dist_tree - -from .dist_to_full import dist_to_full_tree - -from .dsphere_generator import generate_dist_sphere - -from .full_to_dist import full_to_dist_tree - -from .partitioning import partition_dist_tree diff --git a/maia/factory/dcloud_generator.py b/maia/factory/dcloud_generator.py deleted file mode 100644 index 38e8fe3a..00000000 --- a/maia/factory/dcloud_generator.py +++ /dev/null @@ -1,149 +0,0 @@ -import numpy as np -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import par_utils, layouts - -# -------------------------------------------------------------------------- -def _dcloud_to_cgns(dpoint_cloud, comm): - """ - """ - # > Generate dist_tree - n_g_vtx = dpoint_cloud['np_distrib_pts'][comm.size] - dist_zone = PT.new_Zone('zone', size=[[n_g_vtx, 0, 0]], type='Unstructured') - - # > Grid coordinates - cx, cy, cz = layouts.interlaced_to_tuple_coords(dpoint_cloud['np_dpts_coord']) - coords = {'CoordinateX' : cx, 'CoordinateY' : cy, 'CoordinateZ' : cz} - grid_coord = PT.new_GridCoordinates(fields=coords, parent=dist_zone) - - np_distrib_pts = par_utils.full_to_partial_distribution(dpoint_cloud['np_distrib_pts'], comm) - MT.newDistribution({'Vertex' : np_distrib_pts, 'Cell' : np.zeros(3, np_distrib_pts.dtype)}, parent=dist_zone) - - return dist_zone - -# -------------------------------------------------------------------------- -def dpoint_cloud_cartesian_generate(n_vtx, coord_min, coord_max, comm): - """ - This function calls paradigm to generate a distributed set of points a cloud of points, in a cartesian grid, and - return a CGNS PyTree - """ - - assert len(coord_min) == len(coord_max) - cloud_dim = len(coord_min) - assert cloud_dim >= 1 - - if isinstance(n_vtx, int): # Expand scalar to list - n_vtx = cloud_dim * [n_vtx] - assert isinstance(n_vtx, list) - - # Complete to fake 3D - _coord_min = np.empty(3) - _coord_max = np.empty(3) - for i in range(cloud_dim): - _coord_min[i] = coord_min[i] - _coord_max[i] = coord_max[i] - for i in range(cloud_dim, 3): - _coord_min[i] = 0. - _coord_max[i] = 0. - _n_vtx = n_vtx + (3-cloud_dim) * [1] - - dpoint_cloud = PDM.dpoint_cloud_gen_cartesian(comm, *_n_vtx, *_coord_min, *_coord_max) - dist_zone = _dcloud_to_cgns(dpoint_cloud, comm) - - # Remove useless coords if fake 3D was used - for dir in ['Z', 'Y', 'X'][:3-cloud_dim]: - PT.rm_nodes_from_name(dist_zone, f'Coordinate{dir}') - - # Complete tree and return - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=cloud_dim, phy_dim=cloud_dim, parent=dist_tree) - PT.add_child(dist_base, dist_zone) - - return dist_tree - - -# -------------------------------------------------------------------------- -def dpoint_cloud_random_generate(n_g_pts, coord_min, coord_max, comm, seed=None): - """ - This function calls paradigm to generate a distributed set of points a cloud of points, in a random way and - return a CGNS PyTree - """ - assert len(coord_min) == len(coord_max) - cloud_dim = len(coord_min) - - # Complete to fake 3D - _coord_min = np.empty(3) - _coord_max = np.empty(3) - for i in range(cloud_dim): - _coord_min[i] = coord_min[i] - _coord_max[i] = coord_max[i] - for i in range(cloud_dim, 3): - _coord_min[i] = 0. - _coord_max[i] = 0. - - if seed is None: - seed = np.random.randint(np.iinfo(np.int32).max, dtype=np.int32) - - dpoint_cloud = PDM.dpoint_cloud_gen_random(comm, seed, n_g_pts, *_coord_min, *_coord_max) - dist_zone = _dcloud_to_cgns(dpoint_cloud, comm) - - # Remove useless coords if fake 3D was used - for dir in ['Z', 'Y', 'X'][:3-cloud_dim]: - PT.rm_nodes_from_name(dist_zone, f'Coordinate{dir}') - - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=cloud_dim, phy_dim=cloud_dim, parent=dist_tree) - PT.add_child(dist_base, dist_zone) - - return dist_tree - -def generate_dist_points(n_vtx, zone_type, comm, origin=np.zeros(3), max_coords=np.ones(3)): - """Generate a distributed mesh including only cartesian points. - - Returns a distributed CGNSTree containing a single :cgns:`CGNSBase_t` and - :cgns:`Zone_t`. The kind - of the zone is controled by the ``zone_type`` parameter: - - - ``"Structured"`` (or ``"S"``) produces a structured zone - - ``"Unstructured"`` (or ``"U"``) produces an unstructured zone - - In all cases, the created zone contains only the cartesian grid coordinates; no connectivities are created. - The `physical dimension `_ of the output - is set equal to the length of the origin parameter. - - Args: - n_vtx (int or array of int) : Number of vertices in each direction. Scalars - automatically extend to uniform array. - zone_type (str) : requested kind of points cloud - comm (MPIComm) : MPI communicator - origin (array, optional) : Coordinates of the origin of the generated mesh. Defaults - to zero vector. - max_coords (array, optional) : Coordinates of the higher point of the generated mesh. Defaults to ones vector. - Returns: - CGNSTree: distributed cgns tree - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #generate_dist_points@start - :end-before: #generate_dist_points@end - :dedent: 2 - """ - - dist_tree = dpoint_cloud_cartesian_generate(n_vtx, origin, max_coords, comm) - - if zone_type in ["Unstructured", "U"]: - return dist_tree - elif zone_type in ["Structured", "S"]: - for zone in PT.iter_all_Zone_t(dist_tree): - if isinstance(n_vtx, int): - n_vtx = len(origin) * [n_vtx] - zsize = [[_n_vtx, 0, 0] for _n_vtx in n_vtx] - PT.set_value(zone, zsize) - PT.update_child(zone, 'ZoneType', value='Structured') - return dist_tree - else: - raise ValueError(f"Unexpected value for zone_type parameter : {zone_type}") - diff --git a/maia/factory/dcube_generator.py b/maia/factory/dcube_generator.py deleted file mode 100644 index e1d0191b..00000000 --- a/maia/factory/dcube_generator.py +++ /dev/null @@ -1,291 +0,0 @@ -import numpy as np -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.pytree.sids import elements_utils as EU - -import maia -from maia.utils import np_utils, par_utils, layouts - -def _dmesh_nodal_to_cgns_zone(dmesh_nodal, comm, elt_min_dim=0): - - g_dims = dmesh_nodal.dmesh_nodal_get_g_dims() - n_vtx = g_dims['n_vtx_abs'] - n_cell = g_dims['n_face_abs'] if g_dims['n_cell_abs'] == 0 else g_dims['n_cell_abs'] - max_dim = 3 if g_dims['n_cell_abs'] != 0 else 2 - - zone = PT.new_Zone('zone', size=[[n_vtx, n_cell, 0]], type='Unstructured') - - # > Grid coordinates - vtx_data = dmesh_nodal.dmesh_nodal_get_vtx(comm) - cx, cy, cz = layouts.interlaced_to_tuple_coords(vtx_data['np_vtx']) - coords = {'CoordinateX' : cx, 'CoordinateY' : cy, 'CoordinateZ' : cz} - grid_coord = PT.new_GridCoordinates(fields=coords, parent=zone) - - # Carefull ! Getting elt of dim > cell_dim is not allowed - pdm_section_kind = [PDM._PDM_GEOMETRY_KIND_CORNER, PDM._PDM_GEOMETRY_KIND_RIDGE, - PDM._PDM_GEOMETRY_KIND_SURFACIC, PDM._PDM_GEOMETRY_KIND_VOLUMIC] - pdm_section_kind = pdm_section_kind[elt_min_dim:max_dim+1][::-1] - - sections_per_dim = [dmesh_nodal.dmesh_nodal_get_sections(kind, comm) for kind in pdm_section_kind] - - elt_shift = 1 - for dim_sections in sections_per_dim: - for i_section, section in enumerate(dim_sections["sections"]): - cgns_elmt_name = MT.pdm_elts.pdm_elt_name_to_cgns_element_type(section["pdm_type"]) - distrib = par_utils.full_to_partial_distribution(section["np_distrib"], comm) - - _erange = np.array([elt_shift, elt_shift+distrib[-1]-1], section["np_connec"].dtype) - elmt = PT.new_Elements(f"{cgns_elmt_name}.{i_section}", cgns_elmt_name, \ - erange=_erange, econn=section["np_connec"], parent=zone) - MT.newDistribution({'Element' : distrib}, parent=elmt) - elt_shift += distrib[-1] - - # > Distributions - np_distrib_cell = par_utils.uniform_distribution(n_cell, comm) - np_distrib_vtx = par_utils.uniform_distribution(n_vtx, comm) - - MT.newDistribution({'Cell' : np_distrib_cell, 'Vertex' : np_distrib_vtx}, parent=zone) - - return zone - - -# -------------------------------------------------------------------------- -def dcube_generate(n_vtx, edge_length, origin, comm): - """ - This function calls paradigm to generate a distributed mesh of a cube, and - return a CGNS PyTree - """ - if not isinstance(n_vtx, int): - raise NotImplementedError("Poly/NFACE_n generation does not supports variable number of vertices") - dcube = PDM.DCubeGenerator(n_vtx, edge_length, *origin, comm) - - dcube_dims = dcube.dcube_dim_get() - dcube_val = dcube.dcube_val_get() - - distrib_cell = par_utils.dn_to_distribution(dcube_dims['dn_cell'], comm) - distrib_vtx = par_utils.dn_to_distribution(dcube_dims['dn_vtx'], comm) - distrib_face = par_utils.dn_to_distribution(dcube_dims['dn_face'], comm) - distrib_facevtx = par_utils.dn_to_distribution(dcube_dims['sface_vtx'], comm) - - # > Generate dist_tree - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase(parent=dist_tree) - dist_zone = PT.new_Zone('zone', size=[[distrib_vtx[-1], distrib_cell[-1], 0]], - type='Unstructured', parent=dist_base) - - # > Grid coordinates - cx, cy, cz = layouts.interlaced_to_tuple_coords(dcube_val['dvtx_coord']) - coords = {'CoordinateX' : cx, 'CoordinateY' : cy, 'CoordinateZ' : cz} - grid_coord = PT.new_GridCoordinates(fields=coords, parent=dist_zone) - - # > NGon node - dn_face = dcube_dims['dn_face'] - - # > For Offset we have to shift to be global - eso = distrib_facevtx[0] + np_utils.safe_int_cast(dcube_val['dface_vtx_idx'], distrib_face.dtype) - - pe = dcube_val['dface_cell'].reshape(dn_face, 2) - np_utils.shift_nonzeros(pe, distrib_face[-1]) - ngon_n = PT.new_NGonElements('NGonElements', - erange = [1, distrib_face[-1]], parent=dist_zone, - eso = eso, ec = dcube_val['dface_vtx'], pe = pe) - # > BCs - zone_bc = PT.new_ZoneBC(parent=dist_zone) - - face_group_idx = dcube_val['dface_group_idx'] - - face_group = dcube_val['dface_group'] - - bc_names = ['Zmin', 'Zmax', 'Xmin', 'Xmax', 'Ymin', 'Ymax'] - for i_bc in range(dcube_dims['n_face_group']): - bc_n = PT.new_BC(bc_names[i_bc], type='Null', parent=zone_bc) - PT.new_GridLocation('FaceCenter', parent=bc_n) - start, end = face_group_idx[i_bc], face_group_idx[i_bc+1] - dn_face_bnd = end - start - PT.new_IndexArray(value=face_group[start:end].reshape(1,dn_face_bnd), parent=bc_n) - - distrib = par_utils.dn_to_distribution(dn_face_bnd, comm) - MT.newDistribution({'Index' : distrib}, parent=bc_n) - - # > Distributions - MT.newDistribution({'Cell' : distrib_cell, 'Vertex' : distrib_vtx}, parent=dist_zone) - MT.newDistribution({'Element' : distrib_face, 'ElementConnectivity' : distrib_facevtx}, parent=ngon_n) - - return dist_tree - -# -------------------------------------------------------------------------- -def dcube_nodal_generate(n_vtx, edge_length, origin, cgns_elmt_name, comm, get_ridges=False): - """ - This function calls paradigm to generate a distributed mesh of a cube with various type of elements, and - return a CGNS PyTree - """ - - t_elmt = MT.pdm_elts.cgns_elt_name_to_pdm_element_type(cgns_elmt_name) - cgns_elt_index = [prop[0] for prop in EU.elements_properties].index(cgns_elmt_name) - cell_dim = EU.element_dim(cgns_elt_index) - - # Manage 2D meshes with 2D PhyDim - phy_dim = 3 - if len(origin) == 2: - phy_dim = 2 - origin = list(origin) + [0.] - if cell_dim == 3: - assert phy_dim == 3 - - if isinstance(n_vtx, int): - n_vtx = [n_vtx, n_vtx, n_vtx] - assert isinstance(n_vtx, list) and len(n_vtx) == 3 - - dcube = PDM.DCubeNodalGenerator(*n_vtx, edge_length, *origin, t_elmt, 1, comm) - dcube.set_ordering("PDM_HO_ORDERING_CGNS".encode('utf-8')) - dcube.compute() - - dmesh_nodal = dcube.get_dmesh_nodal() - - # > Generate dist_tree - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=cell_dim, phy_dim=phy_dim, parent=dist_tree) - - min_elt_dim = 0 if get_ridges else cell_dim - 1 - dist_zone = _dmesh_nodal_to_cgns_zone(dmesh_nodal, comm, min_elt_dim) - PT.add_child(dist_base, dist_zone) - - if phy_dim == 2: - PT.rm_node_from_path(dist_zone, 'GridCoordinates/CoordinateZ') - - # > BCs - if cell_dim == 2: - bc_names = ['Ymin', 'Ymax', 'Xmin', 'Xmax'] - bc_loc = 'EdgeCenter' - groups = dmesh_nodal.dmesh_nodal_get_group(PDM._PDM_GEOMETRY_KIND_RIDGE) - else: - bc_names = ['Zmin', 'Zmax', 'Xmin', 'Xmax', 'Ymin', 'Ymax'] - bc_loc = 'FaceCenter' - groups = dmesh_nodal.dmesh_nodal_get_group(PDM._PDM_GEOMETRY_KIND_SURFACIC) - - range_per_dim = PT.Zone.get_elt_range_per_dim(dist_zone) - shift_bc = range_per_dim[cell_dim][1] - - zone_bc = PT.new_ZoneBC(parent=dist_zone) - - face_group_idx = groups['dgroup_elmt_idx'] - - face_group = shift_bc + groups['dgroup_elmt'] - n_face_group = face_group_idx.shape[0] - 1 - - for i_bc in range(n_face_group): - bc_n = PT.new_BC(bc_names[i_bc], type='Null', parent=zone_bc) - PT.new_GridLocation(bc_loc, parent=bc_n) - start, end = face_group_idx[i_bc], face_group_idx[i_bc+1] - dn_face_bnd = end - start - PT.new_IndexArray(value=face_group[start:end].reshape(1,dn_face_bnd), parent=bc_n) - MT.newDistribution({'Index' : par_utils.dn_to_distribution(dn_face_bnd, comm)}, parent=bc_n) - - return dist_tree - -def dcube_struct_generate(n_vtx, edge_length, origin, comm, bc_location='Vertex'): - max_coords = np.asarray(origin).copy() + np.asarray(edge_length) - - dist_tree = maia.factory.generate_dist_points(n_vtx, "Structured", comm, origin, max_coords) - dist_base = PT.get_node_from_label(dist_tree, 'CGNSBase_t') - dist_zone = PT.get_node_from_label(dist_tree, 'Zone_t') - - # Update zone dims - zone_dims = PT.get_value(dist_zone) - cell_dim = zone_dims.shape[0] - for dim in range(cell_dim): - zone_dims[dim,1] = zone_dims[dim, 0] - 1 - - # If n_vtx == 1 in one dir, we remove this dir - while zone_dims[-1][1] == 0: - zone_dims = zone_dims[:-1,:] - cell_dim -= 1 - # Update - dist_base[1][0] = cell_dim - PT.set_value(dist_zone, zone_dims) - - # Update Cell distribution and add face distribution - distrib = {'Cell' : par_utils.uniform_distribution(PT.Zone.n_cell(dist_zone), comm)} - if cell_dim == 3: - distrib['Face'] = par_utils.uniform_distribution(PT.Zone.n_face(dist_zone), comm) - MT.newDistribution(distrib, dist_zone) - - # Create BCs - zbc = PT.new_child(dist_zone, 'ZoneBC', 'ZoneBC_t') - - xyz_to_faceloc = {'X':'IFaceCenter', 'Y':'JFaceCenter', 'Z':'KFaceCenter'} - offset = 1 if bc_location=='FaceCenter' else 0 - for idim, dir in enumerate(['X', 'Y', 'Z']): - if cell_dim > idim: - location = bc_location if bc_location=='Vertex' else xyz_to_faceloc[dir] - mask = np_utils.others_mask(np.arange(cell_dim), [idim]) - - pr = np.ones((cell_dim, 2), dtype=zone_dims.dtype) - pr[mask, 1] = zone_dims[mask, 0]-offset - bc = PT.new_BC(f'{dir}min', point_range=pr, loc=location, parent=zbc) - - pr = np.ones((cell_dim, 2), dtype=zone_dims.dtype) - pr[idim, :] = zone_dims[idim,0] - pr[mask, 1] = zone_dims[mask, 0]-offset - bc = PT.new_BC(f'{dir}max', point_range=pr, loc=location, parent=zbc) - - for bc in PT.get_children(zbc): - distri = par_utils.uniform_distribution(PT.Subset.n_elem(bc), comm) - MT.newDistribution({'Index' : distri}, bc) - - return dist_tree - - -def generate_dist_block(n_vtx, cgns_elmt_name, comm, origin=np.zeros(3), edge_length=1.): - """Generate a distributed mesh with a cartesian topology. - - Returns a distributed CGNSTree containing a single :cgns:`CGNSBase_t` and - :cgns:`Zone_t`. The kind - and cell dimension of the zone is controled by the cgns_elmt_name parameter: - - - ``"Structured"`` (or ``"S"``) produces a structured zone, - - ``"Poly"`` produces an unstructured 3d zone with a NGon+PE connectivity, - - ``"NFACE_n"`` produces an unstructured 3d zone with a NFace+NGon connectivity, - - ``"NGON_n"`` produces an unstructured 2d zone with faces described by a NGon - node (not yet implemented), - - Other names must be in ``["TRI_3", "QUAD_4", "TETRA_4", "PYRA_5", "PENTA_6", "HEXA_8"]`` - and produces an unstructured 2d or 3d zone with corresponding standard elements. - - In all cases, the created zone contains the cartesian grid coordinates and the relevant number - of boundary conditions. - - When creating 2 dimensional zones, the - `physical dimension `_ - is set equal to the length of the origin parameter. - - Args: - n_vtx (int or array of int) : Number of vertices in each direction. Scalars - automatically extend to uniform array. - cgns_elmt_name (str) : requested kind of elements - comm (MPIComm) : MPI communicator - origin (array, optional) : Coordinates of the origin of the generated mesh. Defaults - to zero vector. - edge_length (float, optional) : Edge size of the generated mesh. Defaults to 1. - Returns: - CGNSTree: distributed cgns tree - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #generate_dist_block@start - :end-before: #generate_dist_block@end - :dedent: 2 - """ - if cgns_elmt_name in ["Structured", "S"]: - return dcube_struct_generate(n_vtx, edge_length, origin, comm) - elif cgns_elmt_name.upper() in ["POLY", "NFACE_N"]: - dist_tree = dcube_generate(n_vtx, edge_length, origin, comm) - if cgns_elmt_name.upper() == "NFACE_N": - for zone in PT.get_all_Zone_t(dist_tree): - maia.algo.pe_to_nface(zone, comm, removePE=True) - return dist_tree - else: - return dcube_nodal_generate(n_vtx, edge_length, origin, cgns_elmt_name, comm) - diff --git a/maia/factory/dist_from_part.py b/maia/factory/dist_from_part.py deleted file mode 100644 index 6fa73e82..00000000 --- a/maia/factory/dist_from_part.py +++ /dev/null @@ -1,354 +0,0 @@ -from mpi4py import MPI -import numpy as np -import operator -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.algo.dist import matching_jns_tools as MJT -from maia.transfer import utils as tr_utils -from maia.transfer.part_to_dist import data_exchange as PTB -from maia.transfer.part_to_dist import index_exchange as IPTB -from maia.utils import py_utils, par_utils - -def discover_nodes_from_matching(dist_node, part_nodes, queries, comm, - child_list=[], get_value="ancestors", - merge_rule=lambda path:path): - """ - Recreate a distributed structure (basically without data) in dist_node merging all the - path found in (locally known) part_nodes. - Usefull eg to globally reput on a dist_zone some BC created on specific part_zones. - Nodes already present in dist_node will not be added. - dist_node and part_nodes are the starting point of the search to which queries is related - Additional options: - child_list is a list of node names or types related to leaf nodes that will be copied into dist_node - get_value is a list of nodes of the path whose values must be repported to the dist node - get_value can be a list of bool or one of the shortcuts 'all', 'none', 'ancestors' (=all but - last), 'leaf' (only last) - merge_rule accepts a function whose argument is the leaf node path. This function can map the path to an - other, eg to merge splitted node related to a same dist node - Todo : could be optimised using a distributed hash table -> see BM - """ - collected_part_nodes = dict() - for part_node in part_nodes: - for nodes in PT.iter_children_from_predicates(part_node, queries, ancestors=True): - # Apply merge rule to map splitted nodes (eg jn) to the same dist node - leaf_path = merge_rule('/'.join([PT.get_name(node) for node in nodes])) - # Avoid data duplication to minimize exchange - if PT.get_node_from_path(dist_node, leaf_path) is None and leaf_path not in collected_part_nodes: - # Label - labels = [PT.get_label(node) for node in nodes] - - # Values - if isinstance(get_value, str): - get_value = py_utils.str_to_bools(len(nodes), get_value) - if isinstance(get_value, (tuple, list)): - values = [PT.get_value(node) if value else None for node, value in zip(nodes, get_value)] - - # Children - leaf = nodes[-1] - childs = list() - for query in child_list: - # Convert to a list of size 1 to use get_children_from_predicates, who works on a predicate-like list - childs.extend(PT.get_children_from_predicates(leaf, [query])) - collected_part_nodes[leaf_path] = (labels, values, childs) - - for rank_node_path in comm.allgather(collected_part_nodes): - for node_path, (labels, values, childs) in rank_node_path.items(): - if PT.get_node_from_path(dist_node, node_path) is None: - nodes_name = node_path.split('/') - ancestor = dist_node - for name, label, value in zip(nodes_name, labels, values): - ancestor = PT.update_child(ancestor, name, label, value) - # At the end of this loop, ancestor is in fact the leaf node - for child in childs: - PT.add_child(ancestor, child) - -def get_parts_per_blocks(part_tree, comm): - """ - From the partitioned trees, retrieve the paths of the distributed blocks - and return a dictionnary associating each path to the list of the corresponding - partitioned zones - """ - dist_doms = PT.new_CGNSTree() - discover_nodes_from_matching(dist_doms, [part_tree], 'CGNSBase_t/Zone_t', comm, - merge_rule=lambda zpath : MT.conv.get_part_prefix(zpath)) - parts_per_dom = dict() - for zone_path in PT.predicates_to_paths(dist_doms, 'CGNSBase_t/Zone_t'): - parts_per_dom[zone_path] = tr_utils.get_partitioned_zones(part_tree, zone_path) - return parts_per_dom - -def _get_joins_dist_tree(parts_per_dom, comm): - """ - """ - is_face_intra_gc = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] \ - and PT.Subset.GridLocation(n) == 'FaceCenter' \ - and not MT.conv.is_intra_gc(PT.get_name(n)) - has_face_intra_gc = \ - lambda z: PT.get_node_from_predicates(z, ['ZoneGridConnectivity_t', is_face_intra_gc]) is not None - - dist_tree = PT.new_CGNSTree() - for dist_zone_path, part_zones in parts_per_dom.items(): - dist_base_name, dist_zone_name = dist_zone_path.split('/') - dist_base = PT.update_child(dist_tree, dist_base_name, 'CGNSBase_t') - dist_zone = PT.update_child(dist_base, dist_zone_name, 'Zone_t') - - PT.new_child(dist_zone, 'ZoneType', 'ZoneType_t', 'Unstructured') - # Elements are needed only if there are some FaceCenter jns - if par_utils.any_true(part_zones, has_face_intra_gc, comm): - _recover_elements(dist_zone, part_zones, comm) - _recover_GC(dist_zone, part_zones, comm) - - return dist_tree - -def get_joins_dist_tree(part_tree, comm): - """ Recreate a dist tree containing only original jns from - the partitioned tree (with PL). Only for U blocks !""" - parts_per_dom = get_parts_per_blocks(part_tree, comm) - return _get_joins_dist_tree(parts_per_dom, comm) - -def _recover_dist_block_size(part_zones, comm): - """ From a list of partitioned zones (coming from same initial block), - retrieve the size of the initial block """ - intra1to1 = lambda n: PT.get_label(n) == 'GridConnectivity1to1_t' and MT.conv.is_intra_gc(PT.get_name(n)) - - # Collect zone size and pr+opposite zone thought partitioning jns - zones_to_size = {} - zones_to_join = {} - for part_zone in part_zones: - zone_name = PT.get_name(part_zone) - zones_to_size[zone_name] = PT.Zone.CellSize(part_zone) - zones_to_join[zone_name] = [] - for intra_jn in PT.iter_children_from_predicates(part_zone, ['ZoneGridConnectivity_t', intra1to1]): - light_jn = PT.new_GridConnectivity1to1(donor_name=PT.get_value(intra_jn), - point_range=PT.Subset.getPatch(intra_jn)[1]) - zones_to_join[zone_name].append(light_jn) - - # Gather and flatten dicts - zones_to_size_g = {} - zones_to_join_g = {} - for zones_to_size_rank in comm.allgather(zones_to_size): - zones_to_size_g.update(zones_to_size_rank) - for zones_to_join_rank in comm.allgather(zones_to_join): - zones_to_join_g.update(zones_to_join_rank) - - # Choose any starting point - first = next(iter(zones_to_size_g)) - idx_dim = zones_to_size_g[first].size - d_zone_dims = np.zeros((idx_dim,3), np.int32, order='F') - d_zone_dims[:,1] += zones_to_size_g[first] #Cell size - for axis in range(3): - for oper in [operator.ne, operator.eq]: #Go front (vtx != 1), then back (vtx == 1) - # Reset - keep_going = True - current = first - while keep_going: - # Iterate jns and select one to continue in same axis/direction - for jn in zones_to_join_g[current]: - pr = PT.get_child_from_name(jn, 'PointRange')[1] - if PT.Subset.normal_axis(jn) == axis and oper(pr[axis,0], 1): - current = PT.get_value(jn) - d_zone_dims[axis,1] += zones_to_size_g[current][axis] - break - else: #If loop did not break -> we reached the end of block - keep_going = False - d_zone_dims[:,0] = d_zone_dims[:,1] + 1 # Update vertices - return d_zone_dims - -def _recover_elements(dist_zone, part_zones, comm): - # > Get the list of part elements - fake_zone = PT.new_node('Zone', 'Zone_t') #This is just to store the elements - discover_nodes_from_matching(fake_zone, part_zones, 'Elements_t', comm, get_value='leaf') - elt_names = [PT.get_name(elt) for elt in PT.get_children(fake_zone)] - elt_kinds = [PT.Element.CGNSName(elt) for elt in PT.get_children(fake_zone)] - has_ngon = 'NGON_n' in elt_kinds - has_nface = 'NFACE_n' in elt_kinds - - # Deal NGon/NFace - if has_ngon: - assert all([kind in ['NGON_n', 'NFACE_n'] for kind in elt_kinds]) - ngon_name = elt_names[elt_kinds.index('NGON_n')] - IPTB.part_ngon_to_dist_ngon(dist_zone, part_zones, ngon_name, comm) - if has_nface: - nface_name = elt_names[elt_kinds.index('NFACE_n')] - IPTB.part_nface_to_dist_nface(dist_zone, part_zones, nface_name, ngon_name, comm) - # > Shift nface element_range and create all cell distri - n_face_tot = PT.get_node_from_path(dist_zone, f'{ngon_name}/ElementRange')[1][1] - nface_range = PT.get_node_from_path(dist_zone, f'{nface_name}/ElementRange')[1] - nface_range += n_face_tot - - # Deal standard elements - else: - for elt_name in elt_names: - IPTB.part_elt_to_dist_elt(dist_zone, part_zones, elt_name, comm) - - elt_nodes = PT.get_children_from_label(dist_zone, 'Elements_t') #True elements - # > Get shift per dim - n_elt_per_dim = [0,0,0,0] - for elt in elt_nodes: - n_elt_per_dim[PT.Element.Dimension(elt)] += PT.Element.Size(elt) - - elt_order = [PT.Zone.elt_ordering_by_dim(part_zone) for part_zone in part_zones] - n_increase = comm.allreduce(elt_order.count(1), MPI.SUM) - n_decrease = comm.allreduce(elt_order.count(-1), MPI.SUM) - assert n_increase * n_decrease == 0 - - if n_increase > 0: - for elt in elt_nodes: - dim_shift = sum(n_elt_per_dim[:PT.Element.Dimension(elt)]) - ER = PT.get_child_from_name(elt, 'ElementRange') - ER[1] += dim_shift - - else: - for elt in elt_nodes: - dim_shift = sum(n_elt_per_dim[PT.Element.Dimension(elt)+1:]) - ER = PT.get_child_from_name(elt, 'ElementRange') - ER[1] += dim_shift - -def _recover_BC(dist_zone, part_zones, comm): - bc_predicate = ['ZoneBC_t', 'BC_t'] - - discover_nodes_from_matching(dist_zone, part_zones, bc_predicate, comm, - child_list=['FamilyName_t', 'GridLocation_t'], get_value='all') - - for bc_path in PT.predicates_to_paths(dist_zone, bc_predicate): - if PT.Zone.Type(dist_zone) == 'Unstructured': - IPTB.part_pl_to_dist_pl(dist_zone, part_zones, bc_path, comm) - elif PT.Zone.Type(dist_zone) == 'Structured': - IPTB.part_pr_to_dist_pr(dist_zone, part_zones, bc_path, comm) - -def _recover_GC(dist_zone, part_zones, comm): - is_gc = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - is_gc_intra = lambda n: is_gc(n) and not MT.conv.is_intra_gc(PT.get_name(n)) - - gc_predicate = ['ZoneGridConnectivity_t', is_gc_intra] - - discover_nodes_from_matching(dist_zone, part_zones, gc_predicate, comm, - child_list=['GridLocation_t', 'GridConnectivityType_t', 'GridConnectivityProperty_t', - 'GridConnectivityDonorName', 'Transform'], - merge_rule=lambda path: MT.conv.get_split_prefix(path), get_value='leaf') - - #After GC discovery, cleanup donor name suffix - for jn in PT.iter_children_from_predicates(dist_zone, gc_predicate): - val = PT.get_value(jn) - PT.set_value(jn, MT.conv.get_part_prefix(val)) - if PT.GridConnectivity.is1to1(jn): - gc_donor_name = PT.get_child_from_name(jn, 'GridConnectivityDonorName') - PT.set_value(gc_donor_name, MT.conv.get_split_prefix(PT.get_value(gc_donor_name))) - - # Index exchange - for gc_path in PT.predicates_to_paths(dist_zone, gc_predicate): - if PT.Zone.Type(dist_zone) == 'Unstructured': - IPTB.part_pl_to_dist_pl(dist_zone, part_zones, gc_path, comm, True) - elif PT.Zone.Type(dist_zone) == 'Structured': - zgc_name, gc_name = gc_path.split('/') - part_gcs = [PT.get_nodes_from_predicates(part, [zgc_name, gc_name+'*']) for part in part_zones] - part_gcs = py_utils.to_flat_list(part_gcs) - if par_utils.exists_everywhere(part_gcs, 'PointRange', comm): - IPTB.part_pr_to_dist_pr(dist_zone, part_zones, gc_path, comm, True) - elif par_utils.exists_everywhere(part_gcs, 'PointList', comm): - IPTB.part_pl_to_dist_pl(dist_zone, part_zones, gc_path, comm, True) - -def _recover_base_iterative_data(dist_tree, part_tree, comm): - # > Add BaseIterativeData by hand, because we need to manage the names - for dist_base in PT.get_all_CGNSBase_t(dist_tree): - part_base = PT.get_child_from_name(part_tree, PT.get_name(dist_base)) - p_it_data = PT.get_child_from_label(part_base, 'BaseIterativeData_t') - if p_it_data is not None and PT.get_child_from_label(dist_base, 'BaseIterativeData_t') is None: - d_it_data = PT.deep_copy(p_it_data) - p_z_pointers = PT.get_child_from_name(p_it_data, 'ZonePointers') - if p_z_pointers is not None: - part_zp = comm.allgather(PT.get_value(p_z_pointers)) - dist_zp = [] - for i in range(len(part_zp[0])): - znames = [part_zp[ip][i] for ip in range(comm.Get_size())] - znames = [MT.conv.get_part_prefix(elt) for item in znames for elt in item if elt != ''] # Remove suffix + flatten - dist_zp.append( sorted(set(znames)) ) - PT.update_child(d_it_data, 'ZonePointers', 'DataArray_t', value=dist_zp) - PT.update_child(d_it_data, 'NumberOfZones', 'DataArray_t', value=[len(k) for k in dist_zp]) - PT.add_child(dist_base, d_it_data) - -def recover_dist_tree(part_tree, comm): - """ Regenerate a distributed tree from a partitioned tree. - - The partitioned tree should have been created using Maia, or - must at least contains GlobalNumbering nodes as defined by Maia - (see :ref:`part_tree`). - - The following nodes are managed : GridCoordinates, Elements, ZoneBC, ZoneGridConnectivity - FlowSolution, DiscreteData and ZoneSubRegion. - - Args: - part_tree (CGNSTree) : Partitioned CGNS Tree - comm (MPIComm) : MPI communicator - Returns: - CGNSTree: distributed cgns tree - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #recover_dist_tree@start - :end-before: #recover_dist_tree@end - :dedent: 2 - """ - i_rank = comm.Get_rank() - n_rank = comm.Get_size() - - dist_tree = PT.new_CGNSTree() - # > Discover partitioned zones to build dist_tree structure - discover_nodes_from_matching(dist_tree, [part_tree], 'CGNSBase_t', comm, child_list=['Family_t']) - discover_nodes_from_matching(dist_tree, [part_tree], 'CGNSBase_t/Zone_t', comm,\ - child_list = ['ZoneType_t', 'FamilyName_t', 'AdditionalFamilyName_t'], - merge_rule=lambda zpath : MT.conv.get_part_prefix(zpath)) - - _recover_base_iterative_data(dist_tree, part_tree, comm) - - for dist_zone_path in PT.predicates_to_paths(dist_tree, 'CGNSBase_t/Zone_t'): - dist_zone = PT.get_node_from_path(dist_tree, dist_zone_path) - - part_zones = tr_utils.get_partitioned_zones(part_tree, dist_zone_path) - - discover_nodes_from_matching(dist_zone, part_zones, "ZoneIterativeData_t/*", - comm, get_value="all") - - # Create zone distributions - vtx_lngn_list = tr_utils.collect_cgns_g_numbering(part_zones, 'Vertex') - cell_lngn_list = tr_utils.collect_cgns_g_numbering(part_zones, 'Cell') - vtx_distri = PTB._lngn_to_distri(vtx_lngn_list, comm) - cell_distri = PTB._lngn_to_distri(cell_lngn_list, comm) - - MT.newDistribution({'Vertex' : vtx_distri, 'Cell' : cell_distri}, parent=dist_zone) - if PT.Zone.Type(dist_zone) == "Unstructured": - d_zone_dims = np.array([[vtx_distri[2], cell_distri[2], 0]], dtype=np.int32) - elif PT.Zone.Type(dist_zone) == "Structured": - d_zone_dims = _recover_dist_block_size(part_zones, comm) - if d_zone_dims.shape[0] == 3: - face_lngn_list = tr_utils.collect_cgns_g_numbering(part_zones, 'Face') - face_distri = PTB._lngn_to_distri(face_lngn_list, comm) - MT.newDistribution({'Face' : face_distri}, parent=dist_zone) - PT.set_value(dist_zone, d_zone_dims) - - # > Create vertex distribution and exchange vertex coordinates - d_grid_co = PT.new_GridCoordinates('GridCoordinates', parent=dist_zone) - for coord in ['CoordinateX', 'CoordinateY', 'CoordinateZ']: - PT.new_DataArray(coord, value=None, parent=d_grid_co) - PTB.part_coords_to_dist_coords(dist_zone, part_zones, comm) - - # > Create elements - _recover_elements(dist_zone, part_zones, comm) - - # > BND and JNS - _recover_BC(dist_zone, part_zones, comm) - _recover_GC(dist_zone, part_zones, comm) - - # > Flow Solution and Discrete Data - PTB.part_sol_to_dist_sol(dist_zone, part_zones, comm) - PTB.part_discdata_to_dist_discdata(dist_zone, part_zones, comm) - PTB.part_subregion_to_dist_subregion(dist_zone, part_zones, comm) - - # > Todo : BCDataSet - - MJT.copy_donor_subset(dist_tree) - - return dist_tree - diff --git a/maia/factory/dist_to_full.py b/maia/factory/dist_to_full.py deleted file mode 100644 index 5fe16073..00000000 --- a/maia/factory/dist_to_full.py +++ /dev/null @@ -1,49 +0,0 @@ -import maia.pytree as PT -from maia.io import distribution_tree -from maia.algo.dist import redistribute - -def _reshape_S_arrays(tree): - """ Some structured arrays (under FlowSolution_t, GridCoordinates_t) have been - flattened in distributed tree. This function regive them a 2D/3D shape - """ - for zone in PT.get_all_Zone_t(tree): - if PT.Zone.Type(zone) == "Structured": - loc_to_shape = {'Vertex' : PT.Zone.VertexSize(zone), 'CellCenter' : PT.Zone.CellSize(zone)} - for array in PT.get_nodes_from_predicates(zone, 'GridCoordinates_t/DataArray_t'): - array[1] = array[1].reshape(loc_to_shape['Vertex'], order='F') - for container in PT.get_nodes_from_label(zone, 'FlowSolution_t'): - wanted_shape = loc_to_shape[PT.Subset.GridLocation(container)] - for array in PT.get_nodes_from_label(container, 'DataArray_t'): - array[1] = array[1].reshape(wanted_shape, order='F') - -def dist_to_full_tree(dist_tree, comm, target=0): - """ Generate a standard (full) CGNS Tree from a distributed tree. - - The output tree can be used with sequential tools, but is no more compatible with - maia parallel algorithms. - - Args: - dist_tree (CGNSTree) : Distributed CGNS tree - comm (MPIComm) : MPI communicator - target (int, optional) : MPI rank holding the output tree. Defaults to 0. - Returns: - CGNSTree: Full (not distributed) tree or None - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #dist_to_full_tree@start - :end-before: #dist_to_full_tree@end - :dedent: 2 - """ - - full_tree = PT.deep_copy(dist_tree) - - redistribute.redistribute_tree(full_tree, f'gather.{target}', comm) - if comm.Get_rank() == target: - _reshape_S_arrays(full_tree) - distribution_tree.clean_distribution_info(full_tree) - else: - full_tree = None - - return full_tree - diff --git a/maia/factory/dplane_generator.py b/maia/factory/dplane_generator.py deleted file mode 100644 index e7da2601..00000000 --- a/maia/factory/dplane_generator.py +++ /dev/null @@ -1,85 +0,0 @@ -import numpy as np -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import np_utils, par_utils - -# -------------------------------------------------------------------------- -def dplane_generate(xmin, xmax, ymin, ymax, - have_random, init_random, - nx, ny, - comm): - """ - This function calls paradigm to generate a distributed mesh of a cube, and - return a CGNS PyTree - """ - i_rank = comm.Get_rank() - n_rank = comm.Get_size() - - dplane_dict = PDM.PolyMeshSurf(xmin, xmax, ymin, ymax, have_random, init_random, nx, ny, comm) - - # > En 2D -> dn_face == dn_cell - distrib_cell = par_utils.gather_and_shift(dplane_dict['dn_face'],comm, np.int32) - # > En 2D -> dn_vtx == dn_vtx - distri_vtx = par_utils.gather_and_shift(dplane_dict['dn_vtx'],comm, np.int32) - # > En 2D -> dn_edge == dn_face - distrib_face = par_utils.gather_and_shift(dplane_dict['dn_edge'],comm, np.int32) - # > Connectivity by pair - distrib_edge_vtx = par_utils.gather_and_shift(2*dplane_dict['dn_edge'],comm, np.int32) - - # > Generate dist_tree - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=2, phy_dim=2, parent=dist_tree) - dist_zone = PT.new_Zone('zone', size=[[distri_vtx[n_rank], distrib_cell[n_rank], 0]], - type='Unstructured', parent=dist_base) - - # > Grid coordinates - coords = {'CoordinateX' : dplane_dict['dvtx_coord'][0::3], 'CoordinateY' : dplane_dict['dvtx_coord'][1::3]} - grid_coord = PT.new_GridCoordinates(fields=coords, parent=dist_zone) - assert np.max(np.abs(dplane_dict['dvtx_coord'][2::3])) < 1E-16 #In 2D, this one should be zero - - dplane_dict['dedge_vtx_idx'] = np.arange(0, 2*dplane_dict['dn_edge']+1, 2, dtype=dplane_dict['dedge_vtx'].dtype) - assert dplane_dict['dedge_vtx_idx'].shape[0] == dplane_dict['dn_edge']+1 - - # > NGon node - dn_edge = dplane_dict['dn_edge'] - - # > For Offset we have to shift to be global - eso = distrib_edge_vtx[i_rank] + dplane_dict['dedge_vtx_idx'] - - pe = dplane_dict['dedge_face'].reshape(dn_edge, 2) - np_utils.shift_nonzeros(pe, distrib_face[n_rank]) - ngon_n = PT.new_NGonElements('NGonElements', erange = [1, distrib_face[n_rank]], - eso = eso, ec = dplane_dict['dedge_vtx'], pe = pe, parent=dist_zone) - - # > BCs - zone_bc = PT.new_ZoneBC(parent=dist_zone) - - edge_group_idx = dplane_dict['dedge_group_idx'] - edge_group_n = np.diff(edge_group_idx) - - edge_group = dplane_dict['dedge_group'] - - for i_bc in range(dplane_dict['n_edge_group']): - bc_n = PT.new_BC('dplane_bnd_{0}'.format(i_bc), type='BCWall', parent=zone_bc) - PT.new_GridLocation('FaceCenter', parent=bc_n) - start, end = edge_group_idx[i_bc], edge_group_idx[i_bc+1] - dn_edge_bnd = end - start - PT.new_IndexArray(value=edge_group[start:end].reshape(1,dn_edge_bnd), parent=bc_n) - - bc_distrib = par_utils.gather_and_shift(dn_edge_bnd, comm, edge_group.dtype) - distrib = np.array([bc_distrib[i_rank], bc_distrib[i_rank+1], bc_distrib[n_rank]]) - MT.newDistribution({'Index' : distrib}, parent=bc_n) - - # > Distributions - np_distrib_cell = np.array([distrib_cell [i_rank], distrib_cell [i_rank+1], distrib_cell [n_rank]], dtype=pe.dtype) - np_distrib_vtx = np.array([distri_vtx [i_rank], distri_vtx [i_rank+1], distri_vtx [n_rank]], dtype=pe.dtype) - np_distrib_face = np.array([distrib_face [i_rank], distrib_face [i_rank+1], distrib_face [n_rank]], dtype=pe.dtype) - np_distrib_edge_vtx = np.array([distrib_edge_vtx[i_rank], distrib_edge_vtx[i_rank+1], distrib_edge_vtx[n_rank]], dtype=pe.dtype) - - MT.newDistribution({'Cell' : np_distrib_cell, 'Vertex' : np_distrib_vtx}, parent=dist_zone) - MT.newDistribution({'Element' : np_distrib_face, 'ElementConnectivity' : np_distrib_edge_vtx}, parent=ngon_n) - - return dist_tree diff --git a/maia/factory/dsphere_generator.py b/maia/factory/dsphere_generator.py deleted file mode 100644 index 620126ae..00000000 --- a/maia/factory/dsphere_generator.py +++ /dev/null @@ -1,150 +0,0 @@ -import numpy as np -import Pypdm.Pypdm as PDM - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import par_utils, layouts - -from .dcube_generator import _dmesh_nodal_to_cgns_zone - -def dsphere_vol_nodal_generate(n_vtx, radius, origin, comm): - """ Generate a nodal (TETRA_4 + TRI_3) filled sphere """ - dmesh_nodal = PDM.sphere_vol_icosphere_gen_nodal(comm, n_vtx, *origin, radius) - - # > Generate dist_tree - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=3, phy_dim=3, parent=dist_tree) - - dist_zone = _dmesh_nodal_to_cgns_zone(dmesh_nodal, comm, elt_min_dim=2) - PT.add_child(dist_base, dist_zone) - - bc_names = ['Skin'] - groups = dmesh_nodal.dmesh_nodal_get_group(PDM._PDM_GEOMETRY_KIND_SURFACIC) - - range_per_dim = PT.Zone.get_elt_range_per_dim(dist_zone) - shift_bc = range_per_dim[3][1] - - zone_bc = PT.new_ZoneBC(parent=dist_zone) - - face_group_idx = groups['dgroup_elmt_idx'] - - face_group = shift_bc + groups['dgroup_elmt'] - n_face_group = face_group_idx.shape[0] - 1 - - for i_bc in range(n_face_group): - bc_n = PT.new_BC(bc_names[i_bc], type='Null', parent=zone_bc) - PT.new_GridLocation('FaceCenter', parent=bc_n) - start, end = face_group_idx[i_bc], face_group_idx[i_bc+1] - dn_face_bnd = end - start - PT.new_IndexArray(value=face_group[start:end].reshape(1,dn_face_bnd), parent=bc_n) - MT.newDistribution({'Index' : par_utils.dn_to_distribution(dn_face_bnd, comm)}, parent=bc_n) - - return dist_tree - -def dsphere_surf_nodal_generate(n_vtx, radius, origin, comm): - """ Generate a nodal (TRI_3) surfacic sphere """ - dmesh_nodal = PDM.sphere_surf_icosphere_gen_nodal(comm, n_vtx, *origin, radius) - - # > Generate dist_tree - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=2, phy_dim=3, parent=dist_tree) - - dist_zone = _dmesh_nodal_to_cgns_zone(dmesh_nodal, comm, elt_min_dim=1) - PT.add_child(dist_base, dist_zone) - - return dist_tree - -# -------------------------------------------------------------------------- -def dsphere_hollow_nodal_generate(n_vtx, radius_int, radius_ext, origin, comm, n_layer=1, geometric_ratio=1): - """ Generate a nodal (TETRA_4 + TRI_3) partially filled sphere (Spherical crown) - nlayer = number of cell layers in the filled part - geometric_ratio : control the size of cell layers in the filled part (geometric progression) - """ - dmesh_nodal = PDM.sphere_vol_hollow_gen_nodal(comm, n_vtx, n_layer, *origin, radius_int, radius_ext, geometric_ratio) - - # > Generate dist_tree - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase('Base', cell_dim=3, phy_dim=3, parent=dist_tree) - - dist_zone = _dmesh_nodal_to_cgns_zone(dmesh_nodal, comm, elt_min_dim=2) - PT.add_child(dist_base, dist_zone) - - bc_names = ['Skin', 'Farfield'] - groups = dmesh_nodal.dmesh_nodal_get_group(PDM._PDM_GEOMETRY_KIND_SURFACIC) - - range_per_dim = PT.Zone.get_elt_range_per_dim(dist_zone) - shift_bc = range_per_dim[3][1] - - zone_bc = PT.new_ZoneBC(parent=dist_zone) - - face_group_idx = groups['dgroup_elmt_idx'] - - face_group = shift_bc + groups['dgroup_elmt'] - n_face_group = face_group_idx.shape[0] - 1 - - for i_bc in range(n_face_group): - bc_n = PT.new_BC(bc_names[i_bc], type='Null', parent=zone_bc) - PT.new_GridLocation('FaceCenter', parent=bc_n) - start, end = face_group_idx[i_bc], face_group_idx[i_bc+1] - dn_face_bnd = end - start - PT.new_IndexArray(value=face_group[start:end].reshape(1,dn_face_bnd), parent=bc_n) - MT.newDistribution({'Index' : par_utils.dn_to_distribution(dn_face_bnd, comm)}, parent=bc_n) - - return dist_tree - - -# -------------------------------------------------------------------------- - -def generate_dist_sphere(m, cgns_elmt_name, comm, origin=np.zeros(3), radius=1.): - """Generate a distributed mesh with a spherical topology. - - Returns a distributed CGNSTree containing a single :cgns:`CGNSBase_t` and - :cgns:`Zone_t`. The kind - and cell dimension of the zone is controled by the cgns_elmt_name parameter: - - - ``"NFACE_n"`` produces an unstructured 3d zone with a NFace+NGon connectivity, - - ``"NGON_n"`` produces an unstructured 2d zone with a NGon+Bar connectivity, - - Other names must be in ``["TRI_3", "TETRA_4"]`` - and produces an unstructured 2d or 3d zone with corresponding standard elements. - - In all cases, the created zone contains the grid coordinates and the relevant number - of boundary conditions. - - Spherical meshes are - `class I geodesic polyhedra `_ - (icosahedral). Number of vertices on the external surface is equal to - :math:`10m^2+2`. - - - Args: - m (int) : Strict. positive integer who controls the number of vertices (see above) - cgns_elmt_name (str) : requested kind of elements - comm (MPIComm) : MPI communicator - origin (array, optional) : Coordinates of the origin of the generated mesh. Defaults - to zero vector. - radius (float, optional) : Radius of the generated sphere. Defaults to 1. - Returns: - CGNSTree: distributed cgns tree - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #generate_dist_sphere@start - :end-before: #generate_dist_sphere@end - :dedent: 2 - """ - from maia.algo.dist import convert_elements_to_ngon # Cyclic import - - assert m >= 1 - if cgns_elmt_name in ['TETRA_4', 'NFACE_n']: - tree = dsphere_vol_nodal_generate(m-1, radius, origin, comm) - if cgns_elmt_name == 'NFACE_n': - convert_elements_to_ngon(tree, comm) - elif cgns_elmt_name in ['TRI_3', 'NGON_n']: - tree = dsphere_surf_nodal_generate(m-1, radius, origin, comm) - if cgns_elmt_name == 'NGON_n': - convert_elements_to_ngon(tree, comm) - else: - raise ValueError("Unvalid cgns_elmt_name") - - return tree diff --git a/maia/factory/full_to_dist.py b/maia/factory/full_to_dist.py deleted file mode 100644 index e48b0073..00000000 --- a/maia/factory/full_to_dist.py +++ /dev/null @@ -1,249 +0,0 @@ -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.io import distribution_tree -from maia.algo.dist import redistribute -from maia.utils import par_utils, np_utils - -def distribute_pl_node(node, comm): - """ - Distribute a standard node having a PointList (and its childs) over several processes, - using uniform distribution. Mainly useful for unit tests. Node must be know by each process. - """ - dist_node = PT.deep_copy(node) - n_elem = PT.Subset.n_elem(dist_node) - distri = par_utils.uniform_distribution(n_elem, comm) - - #PL and PLDonor - for array_n in PT.get_children_from_predicate(dist_node, 'IndexArray_t'): - array_n[1] = array_n[1][:, distri[0]:distri[1]] - #Data Arrays - has_subset = lambda n : PT.get_child_from_name(n, 'PointList') is not None or PT.get_child_from_name(n, 'PointRange') is not None - bcds_without_pl = lambda n : PT.get_label(n) == 'BCDataSet_t' and not has_subset(n) - bcds_without_pl_query = [bcds_without_pl, 'BCData_t', 'DataArray_t'] - for array_path in ['DataArray_t', 'BCData_t/DataArray_t', bcds_without_pl_query]: - for array_n in PT.iter_children_from_predicates(dist_node, array_path): - array_n[1] = array_n[1][distri[0]:distri[1]] - - #Additionnal treatement for subnodes with PL (eg bcdataset) - has_pl = lambda n : PT.get_name(n) not in ['PointList', 'PointRange'] and has_subset(n) - for child in [node for node in PT.get_children(dist_node) if has_pl(node)]: - dist_child = distribute_pl_node(child, comm) - child[2] = dist_child[2] - - MT.newDistribution({'Index' : distri}, dist_node) - - return dist_node - -def distribute_data_node(node, comm): - """ - Distribute a standard node having arrays supported by allCells or allVertices over several processes, - using uniform distribution. Mainly useful for unit tests. Node must be know by each process. - """ - is_data_array = lambda n: PT.get_label(n) == 'DataArray_t' - assert PT.get_node_from_name(node, 'PointList') is None - dist_node = PT.new_node(PT.get_name(node), PT.get_label(node), PT.get_value(node)) - - for array in PT.iter_children_from_predicate(node, is_data_array): - distri = par_utils.uniform_distribution(array[1].size, comm) - PT.new_DataArray(PT.get_name(array), - (array[1].reshape(-1, order='F')[distri[0] : distri[1]]).copy(), - parent=dist_node) - for child in PT.iter_children_from_predicate(node, lambda n: not is_data_array(n)): - PT.add_child(dist_node, PT.deep_copy(child)) - - return dist_node - -def distribute_element_node(node, comm): - """ - Distribute a standard element node over several processes, using uniform distribution. - Mainly useful for unit tests. Node must be know by each process. - """ - assert PT.get_label(node) == 'Elements_t' - dist_node = PT.deep_copy(node) - - n_elem = PT.Element.Size(node) - distri = par_utils.uniform_distribution(n_elem, comm) - MT.newDistribution({'Element' : distri}, dist_node) - - ec = PT.get_child_from_name(dist_node, 'ElementConnectivity') - if PT.Element.CGNSName(node) in ['NGON_n', 'NFACE_n', 'MIXED']: - eso = PT.get_child_from_name(dist_node, 'ElementStartOffset') - distri_ec = eso[1][[distri[0], distri[1], -1]] - ec[1] = ec[1][distri_ec[0] : distri_ec[1]] - eso[1] = eso[1][distri[0]:distri[1]+1] - - MT.newDistribution({'ElementConnectivity' : np_utils.safe_int_cast(distri_ec, distri.dtype)}, dist_node) - else: - n_vtx = PT.Element.NVtx(node) - ec[1] = ec[1][n_vtx*distri[0] : n_vtx*distri[1]] - MT.newDistribution({'ElementConnectivity' : n_vtx*distri}, dist_node) - - pe = PT.get_child_from_name(dist_node, 'ParentElements') - if pe is not None: - pe[1] = (pe[1][distri[0] : distri[1]]).copy(order='F') #Copy is needed to have contiguous memory - - return dist_node - -def _distribute_tree(tree, comm): - """ - Distribute a standard cgns tree over several processes, using uniform distribution. - Mainly useful for unit tests. Tree must be know by each process. - """ - # Do a copy to capture all original nodes - dist_tree = PT.deep_copy(tree) - for zone in PT.iter_all_Zone_t(dist_tree): - # > Cell & Vertex distribution - n_vtx = PT.Zone.n_vtx(zone) - n_cell = PT.Zone.n_cell(zone) - zone_distri = {'Vertex' : par_utils.uniform_distribution(n_vtx , comm), - 'Cell' : par_utils.uniform_distribution(n_cell, comm)} - if PT.Zone.Type(zone) == 'Structured': - zone_distri['Face'] = par_utils.uniform_distribution(PT.Zone.n_face(zone), comm) - - MT.newDistribution(zone_distri, zone) - - # > Coords - grid_coords = PT.get_children_from_label(zone, 'GridCoordinates_t') - for grid_coord in grid_coords: - PT.rm_child(zone, grid_coord) - PT.add_child(zone, distribute_data_node(grid_coord, comm)) - - # > Elements - elts = PT.get_children_from_label(zone, 'Elements_t') - for elt in elts: - PT.rm_child(zone, elt) - PT.add_child(zone, distribute_element_node(elt, comm)) - - # > Flow Solutions - sols = PT.get_children_from_label(zone, 'FlowSolution_t') + PT.get_children_from_label(zone, 'DiscreteData_t') - for sol in sols: - PT.rm_child(zone, sol) - if PT.get_child_from_name(sol, 'PointList') is None: - PT.add_child(zone, distribute_data_node(sol, comm)) - else: - PT.add_child(zone, distribute_pl_node(sol, comm)) - - # > BCs - zonebcs = PT.get_children_from_label(zone, 'ZoneBC_t') - for zonebc in zonebcs: - PT.rm_child(zone, zonebc) - dist_zonebc = PT.new_child(zone, PT.get_name(zonebc), 'ZoneBC_t') - for bc in PT.iter_children_from_label(zonebc, 'BC_t'): - PT.add_child(dist_zonebc, distribute_pl_node(bc, comm)) - - # > GCs - zonegcs = PT.get_children_from_label(zone, 'ZoneGridConnectivity_t') - for zonegc in zonegcs: - PT.rm_child(zone, zonegc) - dist_zonegc = PT.new_child(zone, PT.get_name(zonegc), 'ZoneGridConnectivity_t') - for gc in PT.get_children_from_label(zonegc, 'GridConnectivity_t') + PT.get_children_from_label(zonegc, 'GridConnectivity1to1_t'): - PT.add_child(dist_zonegc, distribute_pl_node(gc, comm)) - - # > ZoneSubRegion - zone_subregions = PT.get_children_from_label(zone, 'ZoneSubRegion_t') - for zone_subregion in zone_subregions: - # Trick if related to an other node -> add pl - matching_region_path = PT.Subset.ZSRExtent(zone_subregion, zone) - if matching_region_path != PT.get_name(zone_subregion): - PT.add_child(zone_subregion, PT.get_node_from_path(zone, matching_region_path + '/PointList')) - PT.add_child(zone_subregion, PT.get_node_from_path(zone, matching_region_path + '/PointRange')) - dist_zone_subregion = distribute_pl_node(zone_subregion, comm) - if matching_region_path != PT.get_name(zone_subregion): - PT.rm_children_from_name(dist_zone_subregion, 'PointList') - PT.rm_children_from_name(dist_zone_subregion, 'PointRange') - PT.rm_child(dist_zone_subregion, MT.getDistribution(dist_zone_subregion)) - - PT.rm_child(zone, zone_subregion) - PT.add_child(zone, dist_zone_subregion) - - return dist_tree - -def _broadcast_full_to_dist(tree, comm, owner): - """ - Create a distributed tree from a full tree holded by only one proc. - """ - - da_container = ['GridCoordinates_t', 'Elements_t', 'FlowSolution_t', 'DiscreteData_t', 'ZoneSubRegion_t', - 'BC_t', 'BCDataSet_t', 'BCData_t', 'GridConnectivity_t', 'GridConnectivity1to1_t'] - - if comm.Get_rank() == owner: - is_da_container = lambda n: PT.get_label(n) in da_container - is_data_array = lambda n: PT.get_label(n) == 'DataArray_t' and not PT.get_name(n).endswith('#Size') - - # Prepare disttree for owning rank : add #Size node to easily compute distribution and flatten S data - dist_tree = PT.deep_copy(tree) - for zone in PT.iter_all_Zone_t(dist_tree): - for container in PT.iter_nodes_from_predicate(zone, is_da_container, explore='deep'): - for node in PT.get_children_from_predicate(container, 'DataArray_t'): - if PT.get_name(node) != 'ParentElements': - node[1] = node[1].reshape((-1), order='F') - PT.new_node(PT.get_name(node)+'#Size', 'DataArray_t', node[1].shape, parent=container) - for node in PT.get_children_from_predicate(container, 'IndexArray_t'): - PT.new_node(PT.get_name(node)+'#Size', 'DataArray_t', node[1].shape, parent=container) - - # Prepare disttree for other rank: data are empty arrays. #Size node already added - send_size_tree = PT.shallow_copy(dist_tree) - for zone in PT.iter_all_Zone_t(send_size_tree): - for container in PT.iter_nodes_from_predicate(zone, is_da_container, explore='deep'): - for node in PT.get_children_from_predicate(container, is_data_array): - # Be carefull with PE - if PT.get_name(node) == 'ParentElements': - PT.set_value(node, np.empty((0,2), dtype=node[1].dtype, order='F')) - else: - PT.set_value(node, np.empty(0, dtype=node[1].dtype)) - for node in PT.get_children_from_predicate(container, 'IndexArray_t'): - index_dimension = PT.get_child_from_name(container, PT.get_name(node)+'#Size')[1][0] - PT.set_value(node, np.empty((index_dimension,0), dtype=node[1].dtype, order='F')) - else: - send_size_tree = None - - recv_size_tree = comm.bcast(send_size_tree, root=owner) - - # Fix ElementStartOffset depending on receiving rank (0 or cnt#size) - if comm.Get_rank() != owner: - dist_tree = recv_size_tree - for zone in PT.get_all_Zone_t(dist_tree): - for elt in PT.get_children_from_label(zone, 'Elements_t'): - eso_n = PT.get_child_from_name(elt, 'ElementStartOffset') - if eso_n is not None: - ec_size = PT.get_child_from_name(elt, 'ElementConnectivity#Size')[1] - eso_n[1] = (comm.Get_rank() > owner) * np.array(ec_size, dtype=eso_n[1].dtype) - - # Create Distribution nodes from Size nodes - distribution_tree.add_distribution_info(dist_tree, comm, f'gather.{owner}') - PT.rm_nodes_from_name(dist_tree, '*#Size') - - return dist_tree - -def full_to_dist_tree(tree, comm, owner=None): - """ Generate a distributed tree from a standard (full) CGNS Tree. - - Input tree can be defined on a single process (using ``owner = rank_id``), - or a copy can be known by all the processes (using ``owner=None``). - - In both cases, output distributed tree will be equilibrated over all the processes. - - Args: - tree (CGNSTree) : Full (not distributed) tree. - comm (MPIComm) : MPI communicator - owner (int, optional) : MPI rank holding the input tree. Defaults to None. - Returns: - CGNSTree: distributed cgns tree - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #full_to_dist_tree@start - :end-before: #full_to_dist_tree@end - :dedent: 2 - """ - - if owner is not None: - dist_tree = _broadcast_full_to_dist(tree, comm, owner) - redistribute.redistribute_tree(dist_tree, 'uniform', comm) - return dist_tree - else: - return _distribute_tree(tree, comm) - diff --git a/maia/factory/partitioning/__init__.py b/maia/factory/partitioning/__init__.py deleted file mode 100644 index 94ec05de..00000000 --- a/maia/factory/partitioning/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .load_balancing.setup_partition_weights import npart_per_zone as compute_regular_weights -from .load_balancing.setup_partition_weights import balance_multizone_tree as compute_balanced_weights -from .load_balancing.setup_partition_weights import compute_nosplit_weights - -from .partitioning import partition_dist_tree diff --git a/maia/factory/partitioning/gc_name_convention.hpp b/maia/factory/partitioning/gc_name_convention.hpp deleted file mode 100644 index ddc0f9b6..00000000 --- a/maia/factory/partitioning/gc_name_convention.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include "std_e/future/contract.hpp" -#include "std_e/utils/string.hpp" - -namespace maia { - - -inline auto -proc_of_zone(const std::string& zone_name) -> int { - auto tokens = std_e::split(zone_name,'.'); - // Maia convention: zone_name == "name.P{0}.N{1}" - // 0: zone proc - // 1: partition on this proc - int n_tok = tokens.size(); - auto tok = tokens[n_tok-2]; - STD_E_ASSERT(tok[0] == 'P'); - auto proc_str = tok.substr(1); - return std::stoi(proc_str); -} - -inline auto -proc_and_opp_proc_of_grid_connectivity(const std::string& grid_connectivity_name) -> std::pair { - auto tokens = std_e::split(grid_connectivity_name,'.'); - // Maia convention: grid_connectivity_name == "JN.P{0}.N{1}.LT.P{2}.N{3}" - // 0: current zone proc - // 1: partition on this proc - // 2: opposite zone proc - // 3: partition on opposite proc - STD_E_ASSERT(tokens[0] == "JN"); - STD_E_ASSERT(tokens[3] == "LT"); - auto tok = tokens[1]; - STD_E_ASSERT(tok[0] == 'P'); - auto proc_str = tok.substr(1); - auto tok_opp = tokens[4]; - STD_E_ASSERT(tok_opp[0] == 'P'); - auto opp_proc_str = tok_opp.substr(1); - return std::make_pair( - std::stoi(proc_str), - std::stoi(opp_proc_str) - ); -} - -inline auto -opp_proc_of_grid_connectivity(const std::string& grid_connectivity_name) -> int { - return proc_and_opp_proc_of_grid_connectivity(grid_connectivity_name).second; -} - - -} // maia diff --git a/maia/factory/partitioning/load_balancing/balancing_quality.py b/maia/factory/partitioning/load_balancing/balancing_quality.py deleted file mode 100644 index d16565d5..00000000 --- a/maia/factory/partitioning/load_balancing/balancing_quality.py +++ /dev/null @@ -1,83 +0,0 @@ -from mpi4py import MPI -import numpy as np -from math import ceil - -from maia.utils import logging as mlog - -def compute_balance_and_splits_seq(repart_per_zone, display=False): - """ - Evaluate the quality of the input repartition. Input repartition - must be a 2d array of size n_zone * n_rank : cell [i][j] stores - the (int) number of cells affected to the jth rank on the ith zone. - """ - assert repart_per_zone.ndim == 2 - n_zone, n_rank = repart_per_zone.shape - - max_part_size = np.max(repart_per_zone) - min_part_size = np.min(np.ma.masked_equal(repart_per_zone, 0, copy=False)) #min, ignoring 0 - n_cuts = np.count_nonzero(repart_per_zone) - - proc_load = np.sum(repart_per_zone, axis=0) - min_load = np.min(proc_load) - max_load = np.max(proc_load) - - exact_ideal_load = sum(proc_load) / float(n_rank) - ideal_load = ceil(exact_ideal_load) - - imbalance = proc_load - ideal_load - worse_imbalance = np.max(imbalance) - - rms = np.linalg.norm(imbalance) / n_rank - rmscp = np.linalg.norm(imbalance/ideal_load) / n_rank - - if display: - mlog.stat(" ---> Mean size : {0}".format(ideal_load)) - mlog.stat(" ---> rMini size : {0}".format(min_load)) - mlog.stat(" ---> rMaxi size : {0}".format(max_load)) - mlog.stat(" ---> rms : {0}".format(rms)) - mlog.stat(" ---> rmscp : {0}".format(rmscp)) - mlog.stat(" ---> worse delta : {0} ({1:.2f}%)".format(worse_imbalance, 100.*worse_imbalance/ideal_load)) - mlog.stat(" ---> n_cuts : {0}".format(n_cuts)) - - return ideal_load, min_load, max_load, rms, rmscp, n_cuts, min_part_size, max_part_size - -def compute_balance_and_splits(repart_per_zone, comm, display=False): - """ - Evaluate the quality of the input repartition. Input repartition - must be a 1d array of size n_zone : cell [i] stores - the (int) number of cells affected to the ith zone. - - This is a distributed version of compute_balance_and_splits_seq - """ - - n_rank = comm.Get_size() - - max_part_size = comm.allreduce(np.max(repart_per_zone), MPI.MAX) - min_part_size = comm.allreduce(np.min(np.ma.masked_equal(repart_per_zone, 0, copy=False)), MPI.MIN) #min, ignoring 0 - n_cuts = comm.allreduce(np.count_nonzero(repart_per_zone), MPI.SUM) - - proc_load = np.empty(n_rank, dtype=int) - loc_load = repart_per_zone.sum() * np.ones(1, int) - comm.Allgather(loc_load, proc_load) - min_load = np.min(proc_load) - max_load = np.max(proc_load) - - exact_ideal_load = sum(proc_load) / float(n_rank) - ideal_load = ceil(exact_ideal_load) - - imbalance = proc_load - ideal_load - worse_imbalance = np.max(imbalance) - - rms = np.linalg.norm(imbalance) / n_rank - rmscp = np.linalg.norm(imbalance/ideal_load) / n_rank - - if display: - mlog.stat(" ---> Mean size : {0}".format(ideal_load)) - mlog.stat(" ---> rMini size : {0}".format(min_load)) - mlog.stat(" ---> rMaxi size : {0}".format(max_load)) - mlog.stat(" ---> rms : {0}".format(rms)) - mlog.stat(" ---> rmscp : {0}".format(rmscp)) - mlog.stat(" ---> worse delta : {0} ({1:.2f}%)".format(worse_imbalance, 100.*worse_imbalance/ideal_load)) - mlog.stat(" ---> n_cuts : {0}".format(n_cuts)) - - return ideal_load, min_load, max_load, rms, rmscp, n_cuts, min_part_size, max_part_size diff --git a/maia/factory/partitioning/load_balancing/multi_zone_balancing.py b/maia/factory/partitioning/load_balancing/multi_zone_balancing.py deleted file mode 100644 index 3c136048..00000000 --- a/maia/factory/partitioning/load_balancing/multi_zone_balancing.py +++ /dev/null @@ -1,518 +0,0 @@ -from mpi4py import MPI -import logging as LOG -import numpy as np -import heapq - -from cmaia.utils import search_subset_match -from maia.factory.partitioning.load_balancing import single_zone_balancing -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype - -def balance_with_uniform_weights(n_elem_per_zone, n_rank): - """ - Old balacing method. After the procs are affected to the zones, - each zone is shared in equally sized parts. - Allow a proc to be affected to several zones. - Input : n_elem_per_zone (dict) : number of cells in each zone - n_rank (int) : number of available ranks - Output : repart_per_zone (dict) : for each zone, array of size - n_rank indicating the number of cells affected to ith rank. - - May be usefull when weigthed partitioning is not available - """ - # LOG.info(' '*4 + " ~> PrepareDistribBaseGen ") - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - sorted_zone = sorted(n_elem_per_zone.items(), key=lambda item:item[1], reverse=True) - n_elem_zone_abs = {key:value for key,value in sorted_zone} - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # I/ Step 1 - # > Compute mean per rank - n_elem_tot = sum(n_elem_zone_abs.values()) - mini_per_rank = min(n_elem_zone_abs.values()) - maxi_per_rank = max(n_elem_zone_abs.values()) - mean_per_rank = n_elem_tot//n_rank - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Verbose - # LOG.debug(' '*8 + "*"*100) - # LOG.debug(' '*8 + " n_elem_tot : {0} on {1} processors".format(n_elem_tot, n_rank)) - # LOG.debug(' '*8 + " mean_per_rank : {0} ".format(mean_per_rank)) - # LOG.debug(' '*8 + " mini_per_rank : {0} ".format(mini_per_rank)) - # LOG.debug(' '*8 + " maxi_per_rank : {0} ".format(maxi_per_rank)) - # LOG.debug(' '*8 + "*"*100) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Init the dictionnary containing procs list - dproc_to_zone = {key : [] for key in n_elem_zone_abs} - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - cur_rank = 0 - for i_zone, i_elem in n_elem_zone_abs.items(): - # ******************************************* - # > Verbose - # LOG.debug(' '*8 + "-"*100) - # LOG.debug(' '*8 + "~> i_zone/i_elem : {0}/{1} ".format(i_zone, i_elem )) - # LOG.debug(' '*8 + "~> cur_rank : {0} ".format(cur_rank)) - # ******************************************* - - # ******************************************* - # > Compute n_rank affected to the current Zone - n_rank_zone = i_elem//mean_per_rank - # LOG.debug(' '*8 + " ~> n_rank_zone : {0} ".format(n_rank_zone)) - # ******************************************* - - # ******************************************* - for iproc in range(n_rank_zone): - dproc_to_zone[i_zone].append(iproc+cur_rank) - # ******************************************* - - # ******************************************* - # > Compute the number of remaining (rest of integer division ) - r_elem = i_elem - n_rank_zone*mean_per_rank - # LOG.debug(' '*8 + " ~> r_elem : {0} ".format(r_elem)) - # ******************************************* - - # ******************************************* - # > Loop on the next Zone - ii_rank = 0 - for j_zone, j_elem in n_elem_zone_abs.items(): - # ooooooooooooooooooooooooooooooooooooooooo - # > Verbose - LOG.debug(' '*20 + "o"*80 ) - LOG.debug(' '*20 + "~> j_zone/j_elem : {0}/{1} ".format(j_zone, j_elem)) - # ooooooooooooooooooooooooooooooooooooooooo - - # ooooooooooooooooooooooooooooooooooooooooo - # > Check if j_zone have proc already affected - if(len(dproc_to_zone[j_zone]) != 0): - continue # > Zone already affectted - # ooooooooooooooooooooooooooooooooooooooooo - - # ooooooooooooooooooooooooooooooooooooooooo - # > Check if no proc to affect - if(n_rank_zone == 0): - continue # > No proc to affect - # ooooooooooooooooooooooooooooooooooooooooo - - # ooooooooooooooooooooooooooooooooooooooooo - # > Test if j_elem < r_elem - if(j_elem <= r_elem): - # if(ii_rank > n_rank): - if(ii_rank > n_rank_zone): - ii_rank = 0 - dproc_to_zone[j_zone].append(ii_rank+cur_rank) - # LOG.debug(' '*8 + " ~> cur_rank : {0} ".format(cur_rank)) - # LOG.debug(' '*8 + " ~> ii_rank : {0} ".format(ii_rank)) - # LOG.debug(' '*8 + " ~> Add r_elem : {0} to proc : {1} [{2}/{3}] ".format(r_elem, ii_rank+cur_rank, cur_rank, ii_rank)) - ii_rank += 1 - r_elem -= j_elem - # ooooooooooooooooooooooooooooooooooooooooo - - # ******************************************* - cur_rank += n_rank_zone - # ******************************************* - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Some zone have no proc affected and some rank are steal not assigned - n_rank_remain = n_rank - cur_rank - d_remain_zones = {zone : j_elem for zone,j_elem in n_elem_zone_abs.items() if dproc_to_zone[zone] == []} - - # LOG.debug(' '*8 + " ~> Some zone have no proc affected and some rank are steal not assigned : {0}/{1} ".format(d_remain_zones, n_rank_remain)) - - while d_remain_zones: - # LOG.debug(' '*8 + " n_rank_remain : {0} ".format(n_rank_remain)) - - n_elem_remain = 0 - for j_zone, j_elem in d_remain_zones.items(): - if (n_elem_remain + j_elem) <= mean_per_rank: - dproc_to_zone[j_zone].append(cur_rank) - n_elem_remain += j_elem - # LOG.debug(' '*8 + " cur_rank : {0} ".format(cur_rank)) - cur_rank += 1 - - n_rank_remain = n_rank-cur_rank - d_remain_zones = {zone : j_elem for zone,j_elem in d_remain_zones.items() if dproc_to_zone[zone] == []} - - if n_rank_remain == 0: - break - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Some zone have no proc affected and all ranks are assigned - # LOG.debug(' '*8 + " Some zone have no proc affected and all ranks are assigned : {0}/{1} ".format(d_remain_zones, n_rank_remain)) - n_elem_mpi_tmp = np.zeros(n_rank, dtype=np.float64) - for i_zone, lprocs in dproc_to_zone.items(): - n_elem_mpi_tmp[lprocs] += n_elem_per_zone[i_zone]/len(lprocs) - - while d_remain_zones: - # ******************************************* - min_loaded_proc = np.argmin(n_elem_mpi_tmp) - min_load = n_elem_mpi_tmp[min_loaded_proc] - # ******************************************* - - # ******************************************* - cur_zone_to_add, cur_zone_to_add_n_elem = next(iter(d_remain_zones.items())) - # ******************************************* - - # ******************************************* - dproc_to_zone[cur_zone_to_add].append(min_loaded_proc) - n_elem_mpi_tmp[min_loaded_proc] += cur_zone_to_add_n_elem - # ******************************************* - - # ******************************************* - d_remain_zones = {zone : j_elem for zone,j_elem in d_remain_zones.items() if dproc_to_zone[zone] == []} - # ******************************************* - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Some rank are steal not assigned - # LOG.debug(' '*8 + " ~> Some rank are steal not assigned / cur_rank : {0} ".format(cur_rank)) - while(cur_rank != n_rank): - zone_to_delem = {zone : n_elem_per_zone[zone]/len(lprocs) for zone, lprocs in dproc_to_zone.items()} - maxZone = max(zone_to_delem.items(), key=lambda item:item[1])[0] - - # > Fill maxZone - # LOG.debug(' '*8 + " ~> maxZone : {0} ".format(maxZone)) - # LOG.debug(' '*8 + " ~> cur_rank : {0} ".format(cur_rank)) - dproc_to_zone[maxZone].append(cur_rank) - cur_rank += 1 - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Remove double entry - for i_zone in dproc_to_zone.keys(): - dproc_to_zone[i_zone] = list(set(dproc_to_zone[i_zone])) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Format - repart_per_zone = dict() - for zone, procs in dproc_to_zone.items(): - homonegeous_split = single_zone_balancing.homogeneous_repart(n_elem_per_zone[zone], len(procs)) - # > Unpack - homonegeous_split_it = iter(homonegeous_split) - repart_per_zone[zone] = [next(homonegeous_split_it) if i in procs else 0 for i in range(n_rank)] - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - return repart_per_zone - - -def balance_with_non_uniform_weights(n_elem_per_zone, n_rank, - tol_increment=0.01, n_elem_async_mpi=50000): - """ - New balacing method. Assume that zone can be splitted in order to - obtain the prescripted number of elems in each part. - Allow a proc to be affected to several zones. - Input : n_elem_per_zone (dict) : number of cells in each zone - n_rank (int) : number of available ranks - Options : tol_increment - n_elem_async_mpi - Output : repart_per_zone (dict) : for each zone, array of size - n_rank indicating the number of cells affected to ith rank. - - May be usefull when weigthed partitioning is available - """ - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - LOG.info(' '*2 + "================================================================= " ) - LOG.info(' '*2 + "================ Start Computing Distribution ================= " ) - converged = False - adjusted_tol = 0 - initial_allowed_load_pc = 0.75 - min_chunk_size_pc = 0.20 - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # > Sort zones by number of elements - sorted_zone = sorted(n_elem_per_zone.items(), key=lambda item:item[1]) - n_elmt_tot = sum([val for (zone, val) in sorted_zone]) - # > Compute mean per rank - initial_mean_per_rank = n_elmt_tot // n_rank - mean_remainder = n_elmt_tot % n_rank - # > Add one for the remainder - if (mean_remainder) != 0: - initial_mean_per_rank += 1 - - while not converged: - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Init working dictionnary and loading array - n_elem_zone_abs = {key:value for key,value in sorted_zone} - proc_load = np.zeros(n_rank, dtype=np.int32) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Compute new mean_per_rank and thresholds - mean_per_rank = int(initial_mean_per_rank * (1+adjusted_tol)) - allowed_load_ini = int(round(initial_allowed_load_pc * mean_per_rank)) - min_chunk_size = int(round(min_chunk_size_pc * mean_per_rank)) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Verbose - LOG.info(' '*2 + "---------------------------- INPUT ---------------------------- " ) - LOG.info(' '*4 + "nbTotalElem : {0} on {1} processors".format(n_elmt_tot, n_rank)) - LOG.info(' '*4 + "meanPerRank : {0} (remainder was {1})".format(mean_per_rank, mean_remainder)) - LOG.info(' '*4 + "iniMaxLoad : {0}".format(allowed_load_ini)) - LOG.info(' '*4 + "minChunkSize: {0}".format(min_chunk_size)) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Loop over the zones and affect procs - repart_per_zone = {z_name : n_rank*[0] for z_name in n_elem_zone_abs} - current_proc = 0 - - LOG.info(' '*2 + "-------------------------- PROCEDING -------------------------- " ) - - LOG.info(' '*4 + "Step 0. Try to combinate zones to reach meanPerRank") - # > if mean_remainder != 0, we want to combinate zones to find mean_per_rank or mean_per_rank-1 - nb_of_search = 1 if mean_remainder == 0 else 2 - for k in range(nb_of_search): - match_idx = True #To enter the loop - while match_idx: - searching_list = np.fromiter(n_elem_zone_abs.values(), dtype=pdm_gnum_dtype) - match_idx = search_subset_match(searching_list, mean_per_rank-k, 2**20) - if match_idx: - z_names = [list(n_elem_zone_abs.keys())[idx] for idx in match_idx] - LOG.debug(' '*4 + "~>Match found : affect zones {0} to proc {1}".format(z_names, current_proc)) - for z_name in z_names: - n_elem = n_elem_zone_abs.pop(z_name) - repart_per_zone[z_name][current_proc] += n_elem - proc_load[current_proc] += n_elem - current_proc += 1 - - LOG.info(' '*4 + "Step 1. Use small zones to fill procs up to 75% of load") - - i_zone = 0 - n_elem_zone_abs_items = list(n_elem_zone_abs.items()) - while i_zone < len(n_elem_zone_abs_items): - z_name, n_elem = n_elem_zone_abs_items[i_zone] - # ******************************************* - available_size = allowed_load_ini - proc_load[current_proc] - # > La zone est elle assez petite pour le proc en cours? - if n_elem <= available_size: - # LOG.debug(' '*4 + "~>affect zone {0} to proc {1}".format(z_name, current_proc)) - n_elem_zone_abs.pop(z_name) - repart_per_zone[z_name][current_proc] += n_elem - proc_load[current_proc] += n_elem - i_zone += 1 - # > La zone est elle trop grande pour tous les procs ? - elif n_elem > allowed_load_ini: - # LOG.debug(' '*4 + "~>all small zones have been affected") - break - # > Reste il des procs libre pour prendre la zone ? - elif current_proc < n_rank - 1: - current_proc += 1 - else: - # LOG.debug(' '*4 + "~>all procs are loaded to 75%") - break - - LOG.info(' '*4 + "Step 2. Treat remaning zones, allowing cuts") - is_fully_open = np.ones(n_rank, dtype=bool) - for z_name in n_elem_zone_abs: - n_elem = n_elem_zone_abs[z_name] - # LOG.debug(' '*4 + "~>treat zone {0} of size {1}".format(z_name, n_elem)) - while n_elem > 0: - # > Obtention des procs pas encore à 100% - not_full_procs_idx = np.where(proc_load < mean_per_rank-1)[0] - not_full_procs_data = proc_load[not_full_procs_idx] - # > Extraction des procs pas encore à 80% - fully_open_procs_idx = np.extract(is_fully_open[not_full_procs_idx], not_full_procs_idx) - fully_open_procs_data = proc_load[fully_open_procs_idx] - # > Recherche du proc le moins chargé, et du proc le plus chargé parmis ceux < 80% - min_loaded_proc = not_full_procs_idx[np.argmin(not_full_procs_data)] - if fully_open_procs_idx.size > 0: - max_loaded_proc = fully_open_procs_idx[np.argmax(fully_open_procs_data)] - available_size_on_max = mean_per_rank - proc_load[max_loaded_proc] - else: - max_loaded_proc = None - # LOG.debug(' '*4 + " n_elem = {0}, maxProc is {1} (load = {3}/{5}), minProc is {2} (load = {4}/{5})" - # .format(n_elem, max_loaded_proc, min_loaded_proc, proc_load[max_loaded_proc], proc_load[min_loaded_proc], mean_per_rank)) - n_elem_to_load = None - loading_proc = None - - # > Cas où un proc peut se compléter exactement - matching_procs = np.where(proc_load + n_elem == mean_per_rank)[0] - if len(matching_procs) > 0: - # LOG.debug(' '*6 + "~>zone perfectly match with proc {0}".format(matching_procs[0])) - n_elem_to_load = n_elem - loading_proc = matching_procs[0] - - # > Cas où la zone est devenue petite et peut rentrer sur le proc le moins chargé, - # à condition de ne pas dépasser 75% (on revient au step 1) - # avant -> if n_elem <= allowed_load_ini and proc_load[min_loaded_proc]==0: - elif (n_elem + proc_load[min_loaded_proc]) <= allowed_load_ini: - # LOG.debug(' '*6 + "~>zone is now small enought to fit in open proc {0}".format(min_loaded_proc)) - n_elem_to_load = n_elem - loading_proc = min_loaded_proc - - # > Cas où il existe un proc max et qu'il peut se compléter en laissant au moins 20% de la zone - # *et* n_elem > min_chunk_size - # "Ce que je peux charger est inférieur à ce que j'ai le droit de charger" - elif (max_loaded_proc is not None) and (available_size_on_max <= (n_elem - min_chunk_size)): - # LOG.debug(' '*6 + "~>complete proc {0} using {1} elems because remaining chunk will be >20%".format( - # max_loaded_proc, available_size_on_max)) - n_elem_to_load = available_size_on_max - loading_proc = max_loaded_proc - - - # > Cas où le proc max ne peut pas se compléter car la zone est trop grande, - # mais où celle ci peut etre coupée en deux partitions de taille > 20%. - # On cherche - # a. à donner le gros morceau au premier proc pouvant rester sous 80% après chargement - # b. à défaut, au proc qui sera le plus proche de la charge idéale. - elif n_elem >= 2*min_chunk_size: - find_proc = False - min_gap_proc = None - min_gap = 2**32 - - n_elem_to_load = n_elem - min_chunk_size - for iProc in not_full_procs_idx[np.argsort(not_full_procs_data)][::-1]: - available_size_proc = mean_per_rank - proc_load[iProc] # Place courante - potential_load = proc_load[iProc] + n_elem_to_load # Charge si on prends la zone - gap = mean_per_rank - potential_load - if abs(gap) < min_gap: - min_gap = abs(gap) - min_gap_proc = iProc - - # > Equivalent à n_elem_to_load <= available_size_proc - min_chunk_size - # > This is case a - if n_elem <= available_size_proc: - find_proc = True - loading_proc = iProc - # LOG.debug(' '*6 + "~>save a chunk of size 20% and load proc {0} (below 80%) "\ - # "with the remaining {1} elems".format(loading_proc, n_elem - min_chunk_size)) - break - - # > Execute case b. only if no break occured (ie case a did not happend) - else: - loading_proc = min_gap_proc - is_fully_open[loading_proc] = False - # LOG.debug(' '*6 + "~>save a chunk of size 20% and load proc {0} (above 80%, "\ - # "but least worst) with the remaining {1} elems".format(loading_proc, n_elem - min_chunk_size)) - - - # > La zone est < 2*min_chunk_size, mais tous les procs sont déjà chargés à > 75% - else: - n_available_procs = len(not_full_procs_idx) - if n_available_procs > 1 and n_elem > 2*n_elem_async_mpi: - n_elem_to_load1 = int(n_elem/2.) - repart_per_zone[z_name][min_loaded_proc] += n_elem_to_load1 - proc_load[min_loaded_proc] += n_elem_to_load1 - n_elem_zone_abs[z_name] -= n_elem_to_load1 - # Careful - n_elem_to_load has already been added - if mean_per_rank - proc_load[min_loaded_proc] < min_chunk_size: - is_fully_open[min_loaded_proc] = False - - n_elem = n_elem - n_elem_to_load1 - loading_proc = np.argmin(proc_load) - n_elem_to_load = n_elem - # LOG.debug(' '*6 + "~>zone can't be divided but there is no more open proc ; "\ - # "split between procs {0} and {1}".format(min_loaded_proc, loading_proc)) - if mean_per_rank - (proc_load[loading_proc] + n_elem_to_load) < min_chunk_size: - is_fully_open[loading_proc] = False - else: - # LOG.debug(' '*6 + "~>zone can't be divided but there is no more open proc ; "\ - # "affect zone to min proc {0}".format(min_loaded_proc)) - n_elem_to_load = n_elem - loading_proc = min_loaded_proc - if mean_per_rank - (proc_load[loading_proc] + n_elem_to_load) < min_chunk_size: - is_fully_open[loading_proc] = False - - - # > Effective load - repart_per_zone[z_name][loading_proc] += n_elem_to_load - proc_load[loading_proc] += n_elem_to_load - n_elem_zone_abs[z_name] -= n_elem_to_load - n_elem -= n_elem_to_load - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Restart if maxload is to high - converged = np.max(proc_load) <= mean_per_rank - if not converged: - adjusted_tol += tol_increment - LOG.info(' '*2 + '= '*33) - LOG.info(' '*2 + "===== Poor balance -> Increase tolerance to {0:d} % and restart ===== ".format( - int(100*adjusted_tol))) - LOG.info(' '*2 + '= '*33) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - LOG.info(' '*2 + "--------------------------- RUN SUMMARY -------------------------- " ) - LOG.info(' '*4 + "---> Number of runs : {0}".format(int(adjusted_tol/tol_increment)+1)) - LOG.info(' '*4 + "---> Final tolerance used : {0}%".format(int(100*adjusted_tol))) - LOG.info(' '*2 + 66*"-") - # > Check balance - #computeBalanceAndSplits(repart_per_zone, n_rank) - #rMean, rRMS, rMini, rMaxi, rmspc = computeLoadBalance(nbLocalElemPerZone, repart_per_zone, n_rank) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - LOG.info(' '*2 + "================= End Computing Distribution ================= " ) - LOG.info(' '*2 + "================================================================ " ) - return repart_per_zone - -def karmarkar_karp(numbers, n_bin): - """ - Solves the multiway number partitioning using Karmarkar-Karp - algorithm (aka Largest differencing method), see - https://en.wikipedia.org/wiki/Largest_differencing_method - - Reparts the (integer positive) numbers into n_bin bin - such that the largest sum is minimized. - Return a list of size n_bin storing the indices in the numbers - array - """ - subset_weight = lambda s: numbers[s].sum() - numbers = np.asarray(numbers) - all_subsets = [] - for i, number in enumerate(numbers): - # At the begining, max - min is simply the number - ini_subset = [[] for k in range(n_bin-1)] + [[i]] - # heapq is used to easily select the most loaded from list - heapq.heappush(all_subsets, (-number, ini_subset)) - - # Example with numbers = [9,4,7,6,8,5] and n_bin=3 - # After initialisation we have for each number n_bin list: one have the index, - # other are empty. Thanks to heapq the list is order such that max number are first - #[[[],[],[0]], [[],[],[4]], [[],[],[2]], [[],[],[3]], [[],[],[5]], [[],[],[1]]] - # At each step we pop the two first elt of subset list and merge it in reverse - # order: so at step 1, we take [[], [], [0]] and [[], [], [4]], compute the - # sums from number array (0, 0, 9) and (0,0,8) and merge subset - # to obtain [[0], [], [4]]. This merged subset has corresponding sizes (9,0,8), - # so we insert it in ordered list using weight max(sizes) - min(sizes) = 9 - 0 = 9 - # List is now - #[[[0],[],[4]], [[],[],[2]], [[],[],[3]], [[],[],[5]], [[],[],[1]]] - # We select 2 first of size (9,0,8) & (0,0,7) and merge it to [[0], [4], [2]]. - # Sizes are (9, 8, 7) so key for insertion is 9-7 = 2, which make it last of list - #[[[],[],[3]], [[],[],[5]], [[],[],[1]], [[0],[4],[2]]] (Keys = 6, 5, 4, 2) - # Continuing : first to are merged to [[3], [5], []] insered with key 6: - #[[[5],[],[3]], [[],[],[1]], [[0],[4],[2]]] (Keys 6,4,2) - # Continuing : first to are merged to [[3], [5], [1]] insered with key 2: - #[[[3],[5],[1]], [[0],[4],[2]]] (Keys 2,2) - # Corresponding size are (6,5,4) and (9,8,7) so merge gives [[0,1],[4,5],[2,3]] - # which is the final indices for each bin since there is nothing left to merge - - # Only diff below is that we sort (wrt sizes) before insering in list, which - # makes easy the reversed order merge at next iteration - while len(all_subsets) > 1: - # Get the two larger max-min - _, first = heapq.heappop(all_subsets) - _, second = heapq.heappop(all_subsets) - # Combine rule: most loaded of first with least loaded of second and so on - # Because we sorted the subset wrt their size we can just join them - combined_subset = [first[i] + second[n_bin-i-1] for i in range(n_bin)] - - # Sort to prepare next it - combined_subset = sorted(combined_subset, key=subset_weight) - # Prepare next key (max subset size - min subset size) - max_subset, min_subset = combined_subset[-1], combined_subset[0] - next_key = subset_weight(max_subset) - subset_weight(min_subset) - - heapq.heappush(all_subsets, (-next_key, combined_subset)) - - final_subsets = all_subsets[0][1] - return final_subsets diff --git a/maia/factory/partitioning/load_balancing/setup_partition_weights.py b/maia/factory/partitioning/load_balancing/setup_partition_weights.py deleted file mode 100644 index 90d9f8d4..00000000 --- a/maia/factory/partitioning/load_balancing/setup_partition_weights.py +++ /dev/null @@ -1,181 +0,0 @@ -import numpy as np -import logging as LOG - -import maia.pytree as PT -import maia.pytree.sids as SIDS - -from .single_zone_balancing import homogeneous_repart -from .multi_zone_balancing import balance_with_uniform_weights, balance_with_non_uniform_weights, karmarkar_karp - -def compute_nosplit_weights(tree, comm): - """Compute a zone_to_parts repartition without splitting the blocks. - - The initial blocks will be simply distributed over the available processes, - minimizing the total number of cells affected to a proc. This leads to a poor load - balancing and possibly to procs having no partitions at all. - - Args: - tree (CGNSTree) : (Minimal) distributed tree : only zone names and sizes are needed - comm (MPI.Comm) : MPI Communicator - Returns: - dict: ``zone_to_parts`` dictionnary expected by :func:`partition_dist_tree` - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #compute_nosplit_weights@start - :end-before: #compute_nosplit_weights@end - :dedent: 2 - """ - # Note : this is the multiway number partitioning problem - # https://en.wikipedia.org/wiki/Multiway_number_partitioning - i_rank = comm.Get_rank() - n_rank = comm.Get_size() - - zone_paths = PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t') - n_blocks = len(zone_paths) - nb_elts = np.array([SIDS.Zone.n_cell(PT.get_node_from_path(tree, zone_path)) for zone_path in zone_paths]) - if min(nb_elts) == 0: # This is point cloud, without cells, so use vtx to compute balancing - nb_elts = np.array([SIDS.Zone.n_vtx(PT.get_node_from_path(tree, zone_path)) for zone_path in zone_paths]) - - # Early return in simplified cases - repart_per_zone = np.zeros((n_blocks, n_rank), dtype=int) #Full array for statistics - if n_rank == 1: - repart_per_zone[:,0] = nb_elts - elif n_rank >= n_blocks: - repart_per_zone[0:n_blocks,0:n_blocks] = np.diag(nb_elts) - else: - subset_indices = karmarkar_karp(nb_elts, n_rank) - for j, rank_idx in enumerate(subset_indices): - repart_per_zone[rank_idx, j] = nb_elts[rank_idx] - - zone_to_weights = {zone_path: [1.] for j, zone_path in enumerate(zone_paths) \ - if repart_per_zone[j][i_rank] > 0} - - return zone_to_weights - -def npart_per_zone(tree, comm, n_part=1): - """Compute a basic zone_to_parts repartition. - - Each process request n_part partitions on each original zone (n_part can differ - for each proc). - The weights of all the parts produced from a given zone are homogeneous - and equal to the number of cells in the zone divided by the total - number of partitions requested for this zone. - - Args: - tree (CGNSTree) : (Minimal) distributed tree : only zone names and sizes are needed - comm (MPI.Comm) : MPI Communicator - n_part (int,optional) : Number of partitions to produce on each zone by the proc. - Defaults to 1. - Returns: - dict: ``zone_to_parts`` dictionnary expected by :func:`partition_dist_tree` - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #compute_regular_weights@start - :end-before: #compute_regular_weights@end - :dedent: 2 - """ - i_rank = comm.Get_rank() - n_rank = comm.Get_size() - - zone_paths = PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t') - nb_elmt_per_zone = {zone_path : SIDS.Zone.n_cell(PT.get_node_from_path(tree, zone_path)) for zone_path in zone_paths} - if min(nb_elmt_per_zone.values()) == 0: # This is point cloud, without cells, so use vtx to compute balancing - nb_elmt_per_zone = {zone_path : SIDS.Zone.n_vtx(PT.get_node_from_path(tree, zone_path)) for zone_path in zone_paths} - - n_part_np = np.asarray(n_part, dtype=np.int32) - - n_part_shift = np.empty(n_rank+1, dtype=np.int32) - n_part_shift[0] = 0 - n_part_shift_view = n_part_shift[1:] - comm.Allgather(n_part_np, n_part_shift_view) - - n_part_distri = np.cumsum(n_part_shift) - n_part_tot = n_part_distri[n_rank] - - start_idx = n_part_distri[i_rank] - end_idx = n_part_distri[i_rank+1] - repart_per_zone = {zone_path : homogeneous_repart(n_cell, n_part_tot)[start_idx:end_idx] - for zone_path, n_cell in nb_elmt_per_zone.items()} - - zone_to_weights = {zone_path : [k/nb_elmt_per_zone[zone_path] for k in repart] - for zone_path, repart in repart_per_zone.items()} - - return zone_to_weights - -def balance_multizone_tree(tree, comm, only_uniform=False): - """Compute a well balanced zone_to_parts repartition. - - Each process request or not partitions with heterogeneous weight on each - original zone such that: - - - the computational load is well balanced, ie the total number of - cells per process is nearly equal, - - the number of splits within a given zone is minimized, - - produced partitions are not too small. - - Note: - Heterogeneous weights are not managed by ptscotch. Use parmetis as graph_part_tool - for partitioning if repartition was computed with this function, or set optional - argument only_uniform to True. - - Args: - tree (CGNSTree) : (Minimal) distributed tree : only zone names and sizes are needed - comm (MPI.Comm) : MPI Communicator - only_uniform (bool, optional) : If true, an alternative balancing method is used - in order to request homogeneous weights, but load balance is less equilibrated. - Default to False. - - Returns: - dict: ``zone_to_parts`` dictionnary expected by :func:`partition_dist_tree` - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #compute_balanced_weights@start - :end-before: #compute_balanced_weights@end - :dedent: 2 - """ - - i_rank = comm.Get_rank() - n_rank = comm.Get_size() - - zone_paths = PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t') - nb_elmt_per_zone = {zone_path : SIDS.Zone.n_cell(PT.get_node_from_path(tree, zone_path)) for zone_path in zone_paths} - if min(nb_elmt_per_zone.values()) == 0: # This is point cloud, without cells, so use vtx to compute balancing - nb_elmt_per_zone = {zone_path : SIDS.Zone.n_vtx(PT.get_node_from_path(tree, zone_path)) for zone_path in zone_paths} - - repart_per_zone = balance_with_uniform_weights(nb_elmt_per_zone, n_rank) if only_uniform \ - else balance_with_non_uniform_weights(nb_elmt_per_zone, n_rank) - - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Verbose - n_part = [int(n_elmts[i_rank] > 0) for n_elmts in repart_per_zone.values()] - tn_part = [n_rank - n_elmts.count(0) for n_elmts in repart_per_zone.values()] - proc_elmts = [n_elmts[i_rank] for n_elmts in repart_per_zone.values()] - LOG.info(' '*2 + '-'*20 + " REPARTITION FOR RANK {0:04d} ".format(i_rank) + '-'*19) - LOG.info(' '*4 + " zoneName zoneSize : procElem nPart TnPart %ofZone %ofProc") - if sum(proc_elmts) == 0: - LOG.warning(f"Proc {i_rank} was not affected to any zone") - for izone, zone_path in enumerate(repart_per_zone.keys()): - zone_pc = np.around(100*proc_elmts[izone]/nb_elmt_per_zone[zone_path]) - try: - proc_pc = np.around(100*proc_elmts[izone]/sum(proc_elmts)) - except ZeroDivisionError: - proc_pc = 0. - LOG.info(' '*4 + "{0:>12.12} {1:9d} : {2:9d} {3:>5} {4:>6} {5:>6} {6:>6}".format( - zone_path, nb_elmt_per_zone[zone_path], proc_elmts[izone], n_part[izone], tn_part[izone], zone_pc, proc_pc)) - LOG.info('') - tot_pc = np.around(100*sum(proc_elmts)/sum(nb_elmt_per_zone.values())) - LOG.info(' '*4 + " Total {1:9d} : {2:9d} {3:>5} {4:>6} {5:>6} {6:>6}".format( - zone_path, sum(nb_elmt_per_zone.values()), sum(proc_elmts), sum(n_part), sum(tn_part), tot_pc, 100)) - LOG.info(' '*2 + "------------------------------------------------------------------ " ) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # > Convert to expected format and return - zone_to_weights = {zone_path: [repart[i_rank]/nb_elmt_per_zone[zone_path]] \ - for zone_path, repart in repart_per_zone.items() if repart[i_rank] > 0} - return zone_to_weights - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/maia/factory/partitioning/load_balancing/single_zone_balancing.py b/maia/factory/partitioning/load_balancing/single_zone_balancing.py deleted file mode 100644 index 812f59f3..00000000 --- a/maia/factory/partitioning/load_balancing/single_zone_balancing.py +++ /dev/null @@ -1,14 +0,0 @@ -import numpy as np - -def homogeneous_repart(n_elem_zone, n_part): - """ - Split the zone in n_part homogeneous parts. - A basic repartition, mostly usefull for debug. - """ - step = n_elem_zone // n_part - remainder = n_elem_zone % n_part - - zone_repart = step * np.ones(n_part, dtype=np.int32) - zone_repart[:remainder] += 1 - return zone_repart - diff --git a/maia/factory/partitioning/load_balancing/test/test_load_balancing.py b/maia/factory/partitioning/load_balancing/test/test_load_balancing.py deleted file mode 100644 index ff3d0361..00000000 --- a/maia/factory/partitioning/load_balancing/test/test_load_balancing.py +++ /dev/null @@ -1,65 +0,0 @@ -import pytest_parallel - -import numpy as np -from maia.factory.partitioning.load_balancing import multi_zone_balancing -from maia.factory.partitioning.load_balancing import single_zone_balancing -from maia.factory.partitioning.load_balancing import balancing_quality - -class Test_balancing_quality: - def test_single_zone(self): - repart = np.array([[20,20,20,25,20]], dtype=np.int32) - out = np.asarray(balancing_quality.compute_balance_and_splits_seq(repart)) - assert np.all(out[:3] == [21, 20, 25]) - assert np.all(abs(out[3:5] - np.array([0.8944, 0.04259])) < 1E-4) - assert np.all(out[5:] == [5,20,25]) - - def test_single_proc(self): - repart = np.array([[100], [20], [300]], dtype=np.int32) - out = np.asarray(balancing_quality.compute_balance_and_splits_seq(repart)) - assert np.all(out == [420, 420, 420, 0, 0, 3, 20, 300]) - - def test_multiple_A(self): - repart = np.array([[0, 75, 0], - [75, 0,75]], dtype=np.int32) - out = np.asarray(balancing_quality.compute_balance_and_splits_seq(repart)) - assert np.all(out == [75,75,75,0,0,3,75,75]) - - def test_multiple_B(self): - repart = np.array([[50, 50], - [15, 15], - [45, 0], - [ 0, 10]], dtype=np.int32) - out = np.asarray(balancing_quality.compute_balance_and_splits_seq(repart)) - assert np.all(out[:3] == [93, 75, 110]) - assert np.all(abs(out[3:5] - np.array([12.3794, 0.1331])) < 1E-4) - assert np.all(out[5:] == [6,10,50]) - -@pytest_parallel.mark.parallel(2) -def test_balancing_quality_par(comm): - if comm.Get_rank() == 0: - repart = np.array([50, 15, 45, 0]) - elif comm.Get_rank() == 1: - repart = np.array([50, 15, 0, 10]) - out = np.asarray(balancing_quality.compute_balance_and_splits(repart, comm)) - assert np.all(out[:3] == [93, 75, 110]) - assert np.all(abs(out[3:5] - np.array([12.3794, 0.1331])) < 1E-4) - assert np.all(out[5:] == [6,10,50]) - -def test_single_zone_balancing(): - out = single_zone_balancing.homogeneous_repart(30,3) - assert np.all(out == [10,10,10]) - out = single_zone_balancing.homogeneous_repart(31,3) - assert np.all(out == [11,10,10]) - -def test_multi_zone_balancing(): - diczone = {'zoneA' : 100, 'zoneB': 200, 'zoneC':100} - repart_zones = multi_zone_balancing.balance_with_uniform_weights(diczone, 3) - assert repart_zones['zoneA'] == [0, 100, 0] - assert repart_zones['zoneB'] == [200, 0, 0] - assert repart_zones['zoneC'] == [0, 0, 100] - - repart_zones = multi_zone_balancing.balance_with_non_uniform_weights(diczone, 3) - assert repart_zones['zoneA'] == [100, 0, 0] - assert repart_zones['zoneB'] == [34, 34, 132] - assert repart_zones['zoneC'] == [0, 100, 0] - diff --git a/maia/factory/partitioning/load_balancing/test/test_multi_zone_balancing.py b/maia/factory/partitioning/load_balancing/test/test_multi_zone_balancing.py deleted file mode 100644 index cbd1b0e3..00000000 --- a/maia/factory/partitioning/load_balancing/test/test_multi_zone_balancing.py +++ /dev/null @@ -1,10 +0,0 @@ -import pytest - -from maia.factory.partitioning.load_balancing import multi_zone_balancing as ZB - -def test_karmarkar_karp(): - assert ZB.karmarkar_karp([5,9,2,4], 1) == [[1,0,3,2]] - assert ZB.karmarkar_karp([5,9,2,4], 4) == [[2],[3],[0],[1]] - assert ZB.karmarkar_karp([5,9,2,4], 2) == [[3,0], [2,1]] - assert ZB.karmarkar_karp([5,9,1,3], 2) == [[1], [2,0,3]] - assert ZB.karmarkar_karp([9,4,7,6,8,5], 3) == [[1,0], [5,4], [3,2]] diff --git a/maia/factory/partitioning/load_balancing/test/test_setup_partition_weights.py b/maia/factory/partitioning/load_balancing/test/test_setup_partition_weights.py deleted file mode 100644 index 156f0d93..00000000 --- a/maia/factory/partitioning/load_balancing/test/test_setup_partition_weights.py +++ /dev/null @@ -1,94 +0,0 @@ -import pytest_parallel -import numpy as np -from mpi4py import MPI - -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory.partitioning.load_balancing import setup_partition_weights - -@pytest_parallel.mark.parallel(3) -class Test_npart_per_zone_3p: - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneU1 Zone_t [[1331,1000,0]]: - ZoneType ZoneType_t "Unstructured": - ZoneU2 Zone_t [[216,125,0]]: - ZoneType ZoneType_t "Unstructured": - ZoneS Zone_t [[21,20,0],[21,20,0],[2,1,0]]: - ZoneType ZoneType_t "Structured": -""" - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - def test_one_part(self, comm): - zone_to_weights = setup_partition_weights.npart_per_zone(self.dist_tree, comm) - for zone in PT.get_all_Zone_t(self.dist_tree): - assert 'Base0/'+PT.get_name(zone) in zone_to_weights - for zone, weights in zone_to_weights.items(): - assert len(weights) == 1 - assert abs(weights[0] - 1./3.) < 1E-2 #Bad precision due to remainder - - def test_multiple_part(self, comm): - n_part = 2 if comm.Get_rank() == 1 else 1 - zone_to_weights = setup_partition_weights.npart_per_zone(self.dist_tree, comm, n_part) - for zone, weights in zone_to_weights.items(): - assert len(weights) == n_part - for weight in weights: - assert abs(weight - 0.25) < 1E-2 #Bad precision due to remainder - -@pytest_parallel.mark.parallel(2) -class Test_balance_multizone_tree_2p: - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneU1 Zone_t [[1331,1000,0]]: - ZoneType ZoneType_t "Unstructured": - ZoneU2 Zone_t [[216,125,0]]: - ZoneType ZoneType_t "Unstructured": - ZoneS Zone_t [[21,20,0],[21,20,0],[2,1,0]]: - ZoneType ZoneType_t "Structured": -""" - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - - def test_uniform(self, comm): - zone_to_weights = setup_partition_weights.balance_multizone_tree(self.dist_tree, - comm, only_uniform=True) - if comm.Get_rank() == 0: - assert zone_to_weights['Base0/ZoneU1'] == [1.0] - assert zone_to_weights['Base0/ZoneU2'] == [1.0] - assert 'Base0/ZoneS' not in zone_to_weights - elif comm.Get_rank() == 1: - assert 'Base0/ZoneU1' not in zone_to_weights - assert 'Base0/ZoneU2' not in zone_to_weights - assert zone_to_weights['Base0/ZoneS'] == [1.0] - - def test_non_uniform(self, comm): - zone_to_weights = setup_partition_weights.balance_multizone_tree(self.dist_tree, comm) - if comm.Get_rank() == 0: - assert zone_to_weights['Base0/ZoneU1'] == [.238] - assert zone_to_weights['Base0/ZoneU2'] == [1.0] - assert zone_to_weights['Base0/ZoneS'] == [1.0] - elif comm.Get_rank() == 1: - assert zone_to_weights['Base0/ZoneU1'] == [.762] - assert 'Base0/ZoneU2' not in zone_to_weights - assert 'Base0/ZoneS' not in zone_to_weights - -@pytest_parallel.mark.parallel([2,4]) -def test_compute_nosplit_weights(comm): - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneU1 Zone_t [[1331,1000,0]]: - ZoneType ZoneType_t "Unstructured": - ZoneU2 Zone_t [[216,125,0]]: - ZoneType ZoneType_t "Unstructured": - ZoneS Zone_t [[21,20,0],[21,20,0],[2,1,0]]: - ZoneType ZoneType_t "Structured": -""" - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - zone_to_weights = setup_partition_weights.compute_nosplit_weights(dist_tree, comm) - if comm.Get_size() == 2: - if comm.Get_rank() == 0: - assert len(zone_to_weights) == 2 - assert zone_to_weights['Base0/ZoneU2'] == [1.0] - assert zone_to_weights['Base0/ZoneS'] == [1.0] - if comm.Get_rank() == 1: - assert len(zone_to_weights) == 1 - assert zone_to_weights['Base0/ZoneU1'] == [1.0] - assert comm.allreduce(len(zone_to_weights), MPI.SUM) == 3 diff --git a/maia/factory/partitioning/partitioning.py b/maia/factory/partitioning/partitioning.py deleted file mode 100644 index 7b7d03cc..00000000 --- a/maia/factory/partitioning/partitioning.py +++ /dev/null @@ -1,208 +0,0 @@ -import numpy as np -import time -from mpi4py import MPI - -import maia.pytree as PT - -from maia import pdm_has_ptscotch, pdm_has_parmetis -from maia.algo.dist import matching_jns_tools as MJT -from maia.algo.part import connectivity_transform as CNT -from maia.utils import par_utils -from maia.utils import logging as mlog - -from maia.transfer.dist_to_part import data_exchange as BTP - -from .load_balancing import setup_partition_weights as SPW -from .split_S import part_zone as partS -from .split_U import part_all_zones as partU -from .post_split import post_partitioning as post_split -from .load_balancing import balancing_quality - -def set_default(dist_tree, comm): - - default_renum = {'cell_renum_method' : 'NONE', - 'face_renum_method' : 'NONE', - 'vtx_renum_method' : 'NONE', - 'n_cell_per_cache' : 0, - 'n_face_per_pack' : 0, - 'graph_part_tool' : None } - - default = {'graph_part_tool' : None, - 'zone_to_parts' : None, - 'reordering' : default_renum, - 'part_interface_loc' : 'Vertex', - 'output_connectivity' : 'Element', - 'preserve_orientation' : False, - 'save_all_connectivities' : False, - 'additional_ln_to_gn' : [], - 'keep_empty_sections' : False, - 'dump_pdm_output' : False } - - if pdm_has_parmetis: - default['graph_part_tool'] = 'parmetis' - elif pdm_has_ptscotch: - default['graph_part_tool'] = 'ptscotch' - else: - default['graph_part_tool'] = 'hilbert' - default['reordering']['graph_part_tool'] = default['graph_part_tool'] - - # part_interface_loc : Vertex si Elements, FaceCenter si NGons - for zone in PT.get_all_Zone_t(dist_tree): - if 22 in [PT.Element.Type(elt) for elt in PT.iter_children_from_label(zone, 'Elements_t')]: - default['part_interface_loc'] = 'FaceCenter' - break - - return default - -def partition_dist_tree(dist_tree, comm, **kwargs): - """Perform the partitioning operation: create a partitioned tree from the input distributed tree. - - The input tree can be structured or unstuctured, but hybrid meshes are not yet supported. - - Important: - Geometric information (such as boundary conditions, zone subregion, etc.) are reported - on the partitioned tree; however, data fields (BCDataSet, FlowSolution, etc.) are not - transfered automatically. See ``maia.transfer`` module. - - See reference documentation for the description of the keyword arguments. - - Args: - dist_tree (CGNSTree): Distributed tree - comm (MPIComm) : MPI communicator - **kwargs : Partitioning options - Returns: - CGNSTree: partitioned cgns tree - - Example: - .. literalinclude:: snippets/test_factory.py - :start-after: #partition_dist_tree@start - :end-before: #partition_dist_tree@end - :dedent: 2 - """ - - options = set_default(dist_tree, comm) - subkeys = ['reordering'] #Key for which we have sub dicts - - # > Check if input keys exist - for key, val in kwargs.items(): - if key in options.keys(): - if key in subkeys: - assert isinstance(val, dict) - for subkey, subval in val.items(): - if not subkey in options[key].keys(): - mlog.error(f'Partitioning sub keyword "{key}/{subkey}" does not exists and will be ignored') - else: - mlog.error(f'Partitioning keyword "{key}" does not exists and will be ignored') - # > Erase default setting with user settings - for key, val in kwargs.items(): - if key in options.keys(): - if not key in subkeys: - options[key] = val - else: - for subkey, subval in val.items(): - if subkey in options[key].keys(): - options[key][subkey] = subval - # > Check some values - assert options['graph_part_tool'] in partU.maia_to_pdm_split_tool - # TODO we should rename this part_tool because not all methods involve a graph - assert options['part_interface_loc'] in ['Vertex', 'FaceCenter'] - assert options['output_connectivity'] in ['Element', 'NGon'] - - # > Setup balanced weight if no provided - zone_to_parts = options.pop('zone_to_parts') - if zone_to_parts is None: - zone_to_parts = SPW.balance_multizone_tree(dist_tree, comm) - assert isinstance(zone_to_parts, dict) - # > Call main function - part_tree = _partitioning(dist_tree, zone_to_parts, comm, options) - - # Compute statistics - if not par_utils.any_true(zone_to_parts.values(), lambda e: len(e)>1, comm): - # Rebuild array - zone_paths = PT.predicates_to_paths(dist_tree, 'CGNSBase_t/Zone_t') - n_cell_per_block = np.zeros(len(zone_paths), np.int32) - for part_zone_path in PT.predicates_to_paths(part_tree, 'CGNSBase_t/Zone_t'): - part_zone = PT.get_node_from_path(part_tree, part_zone_path) - idx = zone_paths.index(PT.maia.conv.get_part_prefix(part_zone_path)) - n_cell = PT.Zone.n_cell(part_zone) # If zone is a point cloud, use n_vtx - n_cell_per_block[idx] = n_cell if n_cell > 0 else PT.Zone.n_vtx(part_zone) - if comm.Get_rank() == 0: - mlog.stat("[partition_dist_tree] After partitioning, repartition statistics are:") - - balancing_quality.compute_balance_and_splits(n_cell_per_block, comm, comm.Get_rank()==0) - - return part_tree - -def _partitioning(dist_tree, - dzone_to_weighted_parts, - comm, - part_options): - - n_blocks = len(PT.get_all_Zone_t(dist_tree)) - blocks_str = "blocks" if n_blocks > 1 else "block" - mlog.info(f"Partitioning tree of {n_blocks} initial {blocks_str}...") - start = time.time() - is_s_zone = lambda n : PT.get_label(n) == 'Zone_t' and PT.Zone.Type(n) == 'Structured' - is_u_zone = lambda n : PT.get_label(n) == 'Zone_t' and PT.Zone.Type(n) == 'Unstructured' - - MJT.add_joins_donor_name(dist_tree, comm) - - part_tree = PT.new_CGNSTree() - dist_zones_S = [] - part_zones_S = [] - for dist_base in PT.iter_all_CGNSBase_t(dist_tree): - - part_base = PT.new_node(PT.get_name(dist_base), 'CGNSBase_t', PT.get_value(dist_base), parent=part_tree) - #Add top level nodes - for node in PT.get_children(dist_base): - if PT.get_label(node) != "Zone_t": - PT.add_child(part_base, PT.deep_copy(node)) - - #Split S zones : we create a subcom for each zone, to avoid serialization of part_zone - sub_comms = [] - for zone in PT.iter_children_from_predicate(dist_base, is_s_zone): - zone_path = PT.get_name(dist_base) + '/' + PT.get_name(zone) - weights = dzone_to_weighted_parts.get(zone_path, []) - sub_comms.append(comm.Split(len(weights)>0)) - - for zone, sub_comm in zip(PT.iter_children_from_predicate(dist_base, is_s_zone), sub_comms): - zone_path = PT.get_name(dist_base) + '/' + PT.get_name(zone) - weights = dzone_to_weighted_parts.get(zone_path, []) - if len(weights) > 0: - s_parts = partS.part_s_zone(zone, weights, sub_comm, comm.Get_rank()) - for part in s_parts: - PT.add_child(part_base, part) - else: - s_parts = [] - part_zones_S.append(s_parts) - dist_zones_S.append(zone) - - # Transfert coords for S zones, all at once to avoid multiple block_to_parts - BTP.dist_coords_to_part_coords_m(dist_zones_S, part_zones_S, comm) - - - all_s_parts = PT.get_all_Zone_t(part_tree) #At this point we only have S parts - partS.split_original_joins_S(all_s_parts, comm) - - #Split U zones (all at once) - base_to_blocks_u = {PT.get_name(base) : [zone for zone in PT.get_all_Zone_t(base) if is_u_zone(zone)] \ - for base in PT.get_all_CGNSBase_t(dist_tree)} - has_u_zones = any([values != [] for values in base_to_blocks_u.values()]) - if has_u_zones: - base_to_parts_u = partU.part_U_zones(base_to_blocks_u, dzone_to_weighted_parts, comm, part_options) - for base, u_parts in base_to_parts_u.items(): - part_base = PT.get_child_from_name(part_tree, base) - for u_part in u_parts: - if not part_options['preserve_orientation']: - CNT.enforce_boundary_pe_left(u_part) - PT.add_child(part_base, u_part) - - post_split(dist_tree, part_tree, comm) - end = time.time() - n_cell = sum([PT.Zone.n_cell(zone) for zone in PT.iter_all_Zone_t(part_tree)]) - n_cell_all = comm.allreduce(n_cell, MPI.SUM) - mlog.info(f"Partitioning completed ({end-start:.2f} s) -- " - f"Nb of cells for current rank is {mlog.size_to_str(n_cell)} " - f"(Σ={mlog.size_to_str(n_cell_all)})") - - return part_tree diff --git a/maia/factory/partitioning/post_split.py b/maia/factory/partitioning/post_split.py deleted file mode 100644 index 2300025c..00000000 --- a/maia/factory/partitioning/post_split.py +++ /dev/null @@ -1,278 +0,0 @@ -import numpy as np - -import maia -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia.transfer.dist_to_part.index_exchange as IBTP -import maia.transfer.dist_to_part.recover_jn as JBTP - -from maia.utils import s_numbering - -is_zone_s = lambda n: PT.get_label(n) == 'Zone_t' and PT.Zone.Type(n)=='Structured' -is_zone_u = lambda n: PT.get_label(n) == 'Zone_t' and PT.Zone.Type(n)=='Unstructured' -is_initial_match = lambda n : PT.get_label(n) == 'GridConnectivity_t' and PT.GridConnectivity.is1to1(n) \ - and not MT.conv.is_intra_gc(n[0]) - -def pl_as_idx(zone, subset_predicate): - """ - Assume that the PLs found following subset_predicates are (i,j,k) triplets - and convert it to global faces indexes - """ - assert PT.Zone.Type(zone) == 'Structured' - for subset in PT.get_children_from_predicates(zone, subset_predicate): - pl_node = PT.get_node_from_name(subset, 'PointList') - if pl_node is not None: - loc = PT.Subset.GridLocation(subset) - pl = s_numbering.ijk_to_index_from_loc(*pl_node[1], loc, PT.Zone.VertexSize(zone)) - pl_node[1] = pl.reshape((1,-1), order='F') - -def pl_as_ijk(zone, subset_predicate): - """ - Assume that the PLs found following subset_predicates are global faces indexes - and convert it to (i,j,k) triplets - """ - assert PT.Zone.Type(zone) == 'Structured' - for subset in PT.get_children_from_predicates(zone, subset_predicate): - pl_node = PT.get_node_from_name(subset, 'PointList') - if pl_node is not None: - loc = PT.Subset.GridLocation(subset) - pl_ijk = s_numbering.index_to_ijk_from_loc(pl_node[1][0], loc, PT.Zone.VertexSize(zone)) - PT.set_value(pl_node, pl_ijk) - -def update_zone_pointers(part_tree): - """ Update ZonePointers values in BaseIterativeData, if any """ - for base in PT.iter_all_CGNSBase_t(part_tree): - for base_it_data in PT.get_children_from_label(base, 'BaseIterativeData_t'): - zone_pointers_n = PT.get_child_from_name(base_it_data, 'ZonePointers') - if zone_pointers_n is not None: - zone_pointers_all = PT.get_value(zone_pointers_n) - for i, zone_points_inst in enumerate(zone_pointers_all): # Given instant - pznames = [] - for zname in zone_points_inst: # Given zone, for this instant - pzones = PT.get_children_from_predicate(base, lambda n : PT.get_label(n) == 'Zone_t' and \ - MT.conv.get_part_prefix(n[0]) == zname) - pznames.extend([PT.get_name(z) for z in pzones]) - zone_pointers_all[i] = pznames - number_of_zones = [len(k) for k in zone_pointers_all] - if max(number_of_zones) == 0: - # If we have only empty lists, set_value does not understand that it is strings -> enforce it - zone_pointers_all = np.empty( (32,0,len(number_of_zones)), dtype='c', order='F') - zone_pointers_all[:,:,:] = ' ' - PT.set_value(zone_pointers_n, zone_pointers_all) - PT.update_child(base_it_data, 'NumberOfZones', 'DataArray_t', number_of_zones) - -def copy_additional_nodes(dist_zone, part_zone): - """ - """ - #Zone data - names = ['.Solver#Param'] - types = ['FamilyName_t', 'AdditionalFamilyName_t', 'ZoneIterativeData_t'] - for node in PT.get_children(dist_zone): - if PT.get_name(node) in names or PT.get_label(node) in types: - PT.add_child(part_zone, node) - #BCs - names = ['.Solver#BC', 'BoundaryMarker'] - types = ['FamilyName_t', 'AdditionalFamilyName_t', 'ReferenceState_t'] - for p_zbc, p_bc in PT.iter_nodes_from_predicates(part_zone, 'ZoneBC_t/BC_t', ancestors=True): - d_bc = PT.get_node_from_path(dist_zone, PT.get_name(p_zbc)+'/'+PT.get_name(p_bc)) - if d_bc: #Tmp, since S splitting store external JNs as bnd - for node in PT.get_children(d_bc): - if PT.get_name(node) in names or PT.get_label(node) in types: - PT.add_child(p_bc, node) - #GCs - names = ['.Solver#Property', 'GridConnectivityDonorName', 'DistInterfaceId', 'DistInterfaceOrd'] - types = ['FamilyName_t', 'GridConnectivityProperty_t', 'GridConnectivityType_t'] - gc_predicate = 'ZoneGridConnectivity_t/GridConnectivity_t' - for p_zgc, p_gc in PT.iter_nodes_from_predicates(part_zone, gc_predicate, ancestors=True): - d_gc = PT.get_node_from_path(dist_zone, PT.get_name(p_zgc)+'/'+PT.get_name(p_gc)) - if d_gc: #Skip created jns - for node in PT.get_children(d_gc): - if PT.get_name(node) in names or PT.get_label(node) in types: - PT.add_child(p_gc, node) - -def generate_related_zsr(dist_zone, part_zone): - """ - """ - is_inter_gc = lambda n: PT.get_label(n).startswith('GridConnectivity') and not MT.conv.is_intra_gc(PT.get_name(n)) - for d_zsr in PT.iter_nodes_from_predicates(dist_zone, 'ZoneSubRegion_t'): - bc_descriptor = PT.get_child_from_name(d_zsr, 'BCRegionName') - gc_descriptor = PT.get_child_from_name(d_zsr, 'GridConnectivityRegionName') - assert not (bc_descriptor and gc_descriptor) - if bc_descriptor is not None: - bc_name = PT.get_value(bc_descriptor) - bc_n = PT.get_child_from_predicates(part_zone, f'ZoneBC_t/{bc_name}') - if bc_n is not None: - PT.new_ZoneSubRegion(PT.get_name(d_zsr), bc_name=bc_name, parent=part_zone) - elif gc_descriptor is not None: - gc_name = PT.get_value(gc_descriptor) - is_related_gc = lambda n: is_inter_gc(n) and MT.conv.get_split_prefix(PT.get_name(n)) == gc_name - gcs_n = PT.get_children_from_predicates(part_zone, ['ZoneGridConnectivity_t', is_related_gc]) - for gc_n in gcs_n: - pgc_name = PT.get_name(gc_n) - pzsr_name = MT.conv.add_split_suffix(PT.get_name(d_zsr), MT.conv.get_split_suffix(pgc_name)) - PT.new_ZoneSubRegion(pzsr_name, gc_name=pgc_name, parent=part_zone) - -def split_original_joins(p_tree): - """ - """ - for p_base, p_zone in PT.iter_children_from_predicates(p_tree, ['CGNSBase_t', 'Zone_t'], ancestors=True): - d_zone_name = MT.conv.get_part_prefix(p_zone[0]) - for zone_gc in PT.get_children_from_label(p_zone, 'ZoneGridConnectivity_t'): - to_remove = list() - to_append = list() - for gc in PT.get_children_from_label(zone_gc, 'GridConnectivity_t'): - if PT.GridConnectivity.is1to1(gc) and not MT.conv.is_intra_gc(gc[0]): #Skip part joins or non 1to1 jns - pl = PT.get_child_from_name(gc, 'PointList')[1] - pl_d = PT.get_child_from_name(gc, 'PointListDonor')[1] - lngn = PT.get_value(MT.getGlobalNumbering(gc, 'Index')) - donor = PT.get_child_from_name(gc, 'Donor')[1] - # > List of couples (procs, parts) holding the opposite join - opposed_parts = np.unique(donor, axis=0) - for i_sub_jn, opp_part in enumerate(opposed_parts): - join_n = PT.new_GridConnectivity(name = MT.conv.add_split_suffix(PT.get_name(gc), i_sub_jn), - donor_name = MT.conv.add_part_suffix(PT.get_value(gc), *opp_part), - type = 'Abutting1to1') - - matching_faces_idx = np.all(donor == opp_part, axis=1) - - # Extract sub arrays. OK to modify because indexing return a copy - sub_pl = pl [:,matching_faces_idx] - sub_pl_d = pl_d[:,matching_faces_idx] - sub_lngn = lngn[matching_faces_idx] - - # Sort both pl and pld according to min joinId to ensure that - # order is the same - cur_path = p_base[0] + '/' + d_zone_name + '/' + gc[0] - opp_path = PT.GridConnectivity.ZoneDonorPath(gc, p_base[0]) + '/' + PT.get_value(PT.get_child_from_name(gc, 'GridConnectivityDonorName')) - - ref_pl = sub_pl if cur_path < opp_path else sub_pl_d - sort_idx = np.argsort(ref_pl[0]) - sub_pl [0] = sub_pl [0][sort_idx] - sub_pl_d[0] = sub_pl_d[0][sort_idx] - sub_lngn = sub_lngn[sort_idx] - - PT.new_IndexArray(name='PointList' , value=sub_pl , parent=join_n) - PT.new_IndexArray(name='PointListDonor', value=sub_pl_d , parent=join_n) - MT.newGlobalNumbering({'Index' : sub_lngn}, join_n) - #Copy decorative nodes - skip_nodes = ['PointList', 'PointListDonor', ':CGNS#GlobalNumbering', 'Donor', 'GridConnectivityType'] - for node in PT.get_children(gc): - if PT.get_name(node) not in skip_nodes: - PT.add_child(join_n, node) - to_append.append(join_n) - - to_remove.append(PT.get_name(gc)) - for node in to_remove: - PT.rm_children_from_name(zone_gc, node) - for node in to_append: #Append everything at the end; otherwise we may find a new jn when looking for an old one - PT.add_child(zone_gc, node) - -def update_gc_donor_name(part_tree, comm): - """ - Update or add the GridConnectivityDonorName name afted join splitting - """ - is_1to1_gc = lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] \ - and PT.GridConnectivity.is1to1(n) - is_initial_gc = lambda n: is_1to1_gc(n) and not MT.conv.is_intra_gc(PT.get_name(n)) - send_l = [list() for n in range(comm.Get_size())] - for p_base, p_zone in PT.iter_children_from_predicates(part_tree, 'CGNSBase_t/Zone_t', ancestors=True): - for gc in PT.iter_children_from_predicates(p_zone, ['ZoneGridConnectivity_t', is_initial_gc]): - cur_zone_path = PT.get_name(p_base) + '/' + PT.get_name(p_zone) - opp_zone_path = PT.GridConnectivity.ZoneDonorPath(gc, PT.get_name(p_base)) - opp_rank = MT.conv.get_part_suffix(opp_zone_path)[0] - send_l[opp_rank].append((PT.get_name(gc), cur_zone_path, opp_zone_path)) - - recv_l = comm.alltoall(send_l) - - for p_base, p_zone in PT.iter_children_from_predicates(part_tree, 'CGNSBase_t/Zone_t', ancestors=True): - for gc in PT.iter_children_from_predicates(p_zone, ['ZoneGridConnectivity_t', is_1to1_gc]): - cur_zone_path = PT.get_name(p_base) + '/' + PT.get_name(p_zone) - opp_zone_path = PT.GridConnectivity.ZoneDonorPath(gc, PT.get_name(p_base)) - if MT.conv.is_intra_gc(PT.get_name(gc)): - opp_name = MT.conv.name_intra_gc(*MT.conv.get_part_suffix(opp_zone_path), - *MT.conv.get_part_suffix(cur_zone_path)) - PT.new_child(gc, 'GridConnectivityDonorName', 'Descriptor_t', opp_name) - else: - opp_rank = MT.conv.get_part_suffix(opp_zone_path)[0] - candidate_jns = [c[0] for c in recv_l[opp_rank] if c[1:] == (opp_zone_path, cur_zone_path)] - dist_donor_name = PT.get_value(PT.get_child_from_name(gc, 'GridConnectivityDonorName')) - candidate_jns = [jn for jn in candidate_jns if MT.conv.get_split_prefix(jn) == dist_donor_name] - assert len(candidate_jns) == 1 - PT.rm_children_from_name(gc, 'GridConnectivityDonorName') - PT.new_child(gc, 'GridConnectivityDonorName', 'Descriptor_t', candidate_jns[0]) - -def hybrid_jns_as_idx(part_tree): - for s_zone in PT.get_nodes_from_predicate(part_tree, is_zone_s, depth=2): - pl_as_idx(s_zone, ['ZoneGridConnectivity_t', is_initial_match]) - -def hybrid_jns_as_ijk(part_tree, comm): - gc_predicate = ['ZoneGridConnectivity_t', is_initial_match] - zone_s_data = {} - for zone_s_path in PT.predicates_to_paths(part_tree, ['CGNSBase_t', is_zone_s]): - zone_s = PT.get_node_from_path(part_tree, zone_s_path) - pl_as_ijk(zone_s, gc_predicate) - jn_dict = dict() - for gc in PT.get_children_from_predicates(zone_s, gc_predicate): - jn_dict[PT.get_name(gc)] = PT.Subset.GridLocation(gc) - zone_s_data[zone_s_path] = (PT.Zone.CellSize(zone_s), jn_dict) - zone_s_data_all = comm.allgather(zone_s_data) - - for zone_u_path in PT.predicates_to_paths(part_tree, ['CGNSBase_t', is_zone_u]): - basename = PT.path_head(zone_u_path, 1) - zone_u = PT.get_node_from_path(part_tree, zone_u_path) - for gc in PT.get_children_from_predicates(zone_u, gc_predicate): - opp_zone_path = PT.GridConnectivity.ZoneDonorPath(gc, basename) - opp_rank = MT.conv.get_part_suffix(opp_zone_path)[0] - opp_jn_name = PT.get_value(PT.get_child_from_name(gc, 'GridConnectivityDonorName')) - try: - opp_zone_size, opp_zone_jns = zone_s_data_all[opp_rank][opp_zone_path] - opp_loc = opp_zone_jns[opp_jn_name] - pl_donor = PT.get_child_from_name(gc, 'PointListDonor') - pld_ijk = s_numbering.index_to_ijk_from_loc(pl_donor[1][0], opp_loc, opp_zone_size+1) - - PT.set_value(pl_donor, pld_ijk) - except KeyError: - pass # Opp zone is unstructured - -def post_partitioning(dist_tree, part_tree, comm): - """ - """ - dist_zones = PT.get_all_Zone_t(dist_tree) - all_part_zones = PT.get_all_Zone_t(part_tree) - parts_prefix = [MT.conv.get_part_prefix(PT.get_name(zone)) for zone in all_part_zones] - for dist_zone_path in PT.predicates_to_paths(dist_tree, 'CGNSBase_t/Zone_t'): - # Recover matching zones - dist_zone = PT.get_node_from_path(dist_tree, dist_zone_path) - part_zones = maia.transfer.utils.get_partitioned_zones(part_tree, dist_zone_path) - - # Create point list - pl_paths = ['ZoneBC_t/BC_t', 'ZoneBC_t/BC_t/BCDataSet_t', 'ZoneSubRegion_t', - 'FlowSolution_t', 'ZoneGridConnectivity_t/GridConnectivity_t'] - IBTP.dist_pl_to_part_pl(dist_zone, part_zones, pl_paths, 'Elements', comm) - for p_zone in part_zones: - PT.rm_children_from_label(p_zone, 'FakeElements_t') - IBTP.dist_pl_to_part_pl(dist_zone, part_zones, pl_paths, 'Vertex' , comm) - if PT.Zone.Type(dist_zone) == 'Structured': - if PT.Zone.CellSize(dist_zone).size == 3: - IBTP.dist_pl_to_part_pl(dist_zone, part_zones, pl_paths, 'SFace', comm) - for part_zone in part_zones: - copy_additional_nodes(dist_zone, part_zone) - - # Next functions works on S meshes if PointList refers to global faces indices - hybrid_jns_as_idx(part_tree) - - # Match original joins - JBTP.get_pl_donor(dist_tree, part_tree, comm) - split_original_joins(part_tree) - for dist_zone_path in PT.predicates_to_paths(dist_tree, 'CGNSBase_t/Zone_t'): - dist_zone = PT.get_node_from_path(dist_tree, dist_zone_path) - part_zones = maia.transfer.utils.get_partitioned_zones(part_tree, dist_zone_path) - for part_zone in part_zones: - generate_related_zsr(dist_zone, part_zone) # Make BC_ZSR and GC_ZSR - update_gc_donor_name(part_tree, comm) - - # Go back to ijk for PointList - hybrid_jns_as_ijk(part_tree, comm) - - update_zone_pointers(part_tree) diff --git a/maia/factory/partitioning/split_S/balancing_cut_tree.py b/maia/factory/partitioning/split_S/balancing_cut_tree.py deleted file mode 100644 index beb19cb8..00000000 --- a/maia/factory/partitioning/split_S/balancing_cut_tree.py +++ /dev/null @@ -1,133 +0,0 @@ -r""" -This module implements a balancing cut tree structure -and some algorithm, detailed in https://doi.org/10.1016/j.jpdc.2019.12.010, -useful for structured blocks partitioning. - -Trees are represented by nested list. A l levels tree is a (l-1) nested list - and at a given level k, we find: - - either a list of sub nodes (if k < l) - - either an interger value (if k == l). This value represent the number - of terminal leaves in this branch of the tree. - _ o _ - / | \ For example, this tree is represented by - / | \ T = [ [[2]], [[1], [3]], [[1], [2]] ] - a b c We have len(tree) = 3, meaning that 3 branches start - | /\ |\ from top level node (let call them a, b, c); - | / \ | \ since branch b leads to two node, len(b:=T[1]) = 2; - o o o o o unlikely, len(a:=T[0]) = 1 because branch a continues - /| | /|\ | |\ in one node. Going deeper in tree, we are now almost at - / | | / | \ | | \ terminal level : nodes have child who are terminal leaves. -o o o o o oo o o Thus, we juste store in the structure the number of this leaves, - that is eg a[0] = T[0][0] = [2] ; b[0] = T[1][0] = [1] - -In the context of blocks partitioning, trees represent the number of cuts of the block, -each level beeing associated to a spatial dimension. - ______________ - / / / /| Previous tree would thus represent a 3d block (because deepth(L) = 3) - / /___/ / | which is split in 3 parts along the x axis : - / / /_____/ /| - first part is not splitted in y axis, but it splitted in two along -/___/___/____ /|/ | z axis -| | | | | / - second part and third are both splitted in two along y axis, but -|___| | | |/ k the two resulting chunks of the second part are respectively splitted -| | | | / ^ in 1 and 3 along z axis, whereas the two resulting chunks of the third -|___|___|____|/ | part are respectively splitted in 1 and 2 along z axis. - --> i -Note that the balancing cut tree only indicates the number of cuts. Position of cuts -are computed using the number of cells in each direction + some weight information, and -are also taken into account when cut tree are refined (ie when new partition are insered) -""" - -def init_cut_tree(n_dims): - """Create an empty tree (1 leave) with the requested number of levels""" - tree = 1 - for i in range(n_dims): - tree = [tree] - return tree - -def reset_sub_tree(node): - """ From a given node, traverse (recursively) childrens to set - their number of leaves to 1 """ - if depth(node) > 1: - for child in node: - reset_sub_tree(child) - else: - node[0] = 1 - -def depth(l): - """Return the depth of a nested list and 0 if l is not a list""" - result = isinstance(l, list) and max(map(depth, l))+1 - return result if result else 0 - -def sum_leaves(node): - """Sums recursively the leaves from a given node""" - if isinstance(node, list): - n_leaves = 0 - for child in node: - n_leaves += sum_leaves(child) - else: - n_leaves = node - return n_leaves - -def weight(node, dims): - """Compute the weight associated to a node, defined as the - number of childs of this node (non recursively count) - divided by the dimension corresponding to the level of the node - """ - n_dims = len(dims) - dep = depth(node) - if dep > 1: - return len(node) / dims[n_dims - dep] - elif dep == 1: - return node[0] / dims[n_dims - dep] - else: - raise ValueError('Can not compute weight of last level node') - -def child_with_least_leaves(node): - """ Compare the children of a given node and return the - one having the least leaves. If tie, return first found (leftmost)""" - candidate = node[0] - for child in node: - if sum_leaves(child) < sum_leaves(candidate): - candidate = child - return candidate - -def select_insertion(starting_node, insersion_node, dims): - """ Select the position in tree where a new node will be insered - ie the node for which weight is minimal - """ - if weight(starting_node, dims) < weight(insersion_node, dims): - insersion_node = starting_node - if depth(starting_node) > 1: - next_node = child_with_least_leaves(starting_node) - insersion_node = select_insertion(next_node, insersion_node, dims) - return insersion_node - - -def insert_child_at(node, dims): - """ Insert a new child a the given position. If node is - not base level, its subtree is reseted and recomputed nd in order - to have a well balanced tree - """ - n_leaves_ini = sum_leaves(node) - if depth(node) == 0: - raise ValueError("invalid node in insertChildAt") - elif depth(node) == 1: - node[0] += 1 - else: - for child in node: - reset_sub_tree(child) - #Create new node with good dim - newNode = init_cut_tree(depth(node)-1) - node.append(newNode) - - while (sum_leaves(node) != n_leaves_ini+1): - nextins = select_insertion(node, node, dims) - insert_child_at(nextins, dims) - -def refine_cut_tree(cut_tree, dims): - """Refine a cut_tree ie select an insertion position - add a new child at this position - """ - ins = select_insertion(cut_tree, cut_tree, dims) - insert_child_at(ins, dims) - diff --git a/maia/factory/partitioning/split_S/part_zone.py b/maia/factory/partitioning/split_S/part_zone.py deleted file mode 100644 index 1062bfc2..00000000 --- a/maia/factory/partitioning/split_S/part_zone.py +++ /dev/null @@ -1,485 +0,0 @@ -from mpi4py import MPI -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.utils import np_utils, s_numbering -from . import split_cut_tree as SCT - -idx_to_dir = {0:'x', 1:'y', 2:'z'} -dir_to_idx = {'x':0, 'y':1, 'z':2} -min_max_as_int = lambda st : 0 if 'min' in st else 1 - -def zone_cell_range(zone): - """ Return the size of a point_range 2d array """ - n_cell = PT.Zone.CellSize(zone) - zone_range = np.empty((n_cell.shape[0], 2), n_cell.dtype) - zone_range[:,0] = 1 - zone_range[:,1] = n_cell - return zone_range - -def collect_S_bnd_per_dir(zone): - """ - Group the bc and 1to1 gc founds in a structured zone according to - the geometric boundary they belong (xmin, xmax, ymin, ... , zmax). - Return a dictionnary storing the dist boundaries for - each geometric boundary. - """ - base_bound = {k : [] for k in ["xmin", "ymin", "zmin", "xmax", "ymax", "zmax"]} - - bnd_queries = [['ZoneBC_t', 'BC_t'], - ['ZoneGridConnectivity_t', lambda n : PT.get_label(n) in ['GridConnectivity1to1_t', 'GridConnectivity_t']], - ['ZoneBC_t', 'BC_t', 'BCDataSet_t']] - for bnd_query in bnd_queries: - for nodes in PT.iter_children_from_predicates(zone, bnd_query, ancestors=True): - bnd = nodes[-1] - point_range_n = PT.get_node_from_name(bnd, 'PointRange') - if point_range_n is not None: #Skip BC/GC defined by a PointList -- they will be updated after - point_range = point_range_n[1] - bnd_normal_index = PT.Subset.normal_axis(bnd) - - if PT.get_label(bnd) == 'BCDataSet_t': - bcds_path = '/'.join([PT.get_name(n) for n in nodes[:-1]]) - PT.update_child(bnd, '__maia::dspath', 'Descriptor_t', bcds_path) - - pr_val = point_range[bnd_normal_index,0] - extr = 'min' if pr_val == 1 else 'max' - - base_bound[idx_to_dir[bnd_normal_index] + extr].append(bnd) - - # Remove extra dims - base_bound = {key:val for key, val in base_bound.items() if val != []} - return base_bound - -def intersect_pr(prA, prB): - """ - Compute the intersection of two numpy array of shape(n,2) - Return None if arrays are disjoints - """ - assert prA.ndim == 2 and prA.shape[1] == 2 - assert prB.shape == prA.shape - - sub_pr = np.empty_like(prA) - for d in range(prA.shape[0]): - if prB[d,0] <= prA[d,0] and prA[d,0] <= prB[d,1]: - sub_pr[d,0] = prA[d,0] - elif prA[d,0] <= prB[d,0] and prB[d,0] <= prA[d,1]: - sub_pr[d,0] = prB[d,0] - else: - return None - sub_pr[d,1] = min(prA[d,1], prB[d,1]) - - return sub_pr - -def pr_to_cell_location(pr, normal_idx, original_loc, bnd_is_max, reverse=False): - """ - Shift the input point_range to change its location from original_loc to cells - (if reverse = False) or from cells to its original_loc (if reverse = True) - """ - not_normal_idx = np.where(np.arange(pr.shape[0]) != normal_idx)[0] - sign = -1 if reverse else 1 - if not original_loc == 'CellCenter': - pr[normal_idx,:] -= sign*int(bnd_is_max) - if original_loc == 'Vertex': - pr[not_normal_idx,1] -= sign*1 - -def pr_to_global_num(pr, zone_offset, reverse=False): - """ - Shift the input point_range to have it in the global numbering of the dist_zone - (if reverse = False) or to go back in the local (if reverse = True) - """ - sign = -1 if reverse else 1 - pr += sign*(zone_offset.reshape((-1,1)) - 1) - -def create_bcs(d_zone, p_zone, p_zone_offset): - """ - """ - zbc = PT.new_ZoneBC(parent=p_zone) - for geo_bnd, dist_bnds in collect_S_bnd_per_dir(d_zone).items(): - - normal_idx = dir_to_idx[geo_bnd[0]] - extr = min_max_as_int(geo_bnd) #0 if min, 1 if max - - # Get the part boundary in absolute cell numbering - range_part_bc_g = zone_cell_range(p_zone) - range_part_bc_g[normal_idx, 1-extr] = range_part_bc_g[normal_idx, extr] - pr_to_global_num(range_part_bc_g, p_zone_offset) - - #Check if part boundary is internal or comes from an old BC or GC - is_old_bc = range_part_bc_g[normal_idx,extr] == zone_cell_range(d_zone)[normal_idx,extr] - if is_old_bc: - idx_dim = range_part_bc_g.shape[0] - dirs = np.where(np.arange(idx_dim) != normal_idx)[0] - for dist_bc in dist_bnds: - dist_bc_pr_n = PT.get_child_from_name(dist_bc, 'PointRange') - range_dist_bc = np.copy(dist_bc_pr_n[1]) - grid_loc = PT.Subset.GridLocation(dist_bc) - - #Swap because some gc are allowed to be reversed and convert to cell - dir_to_swap = (range_dist_bc[:,1] < range_dist_bc[:,0]) - range_dist_bc[dir_to_swap, 0], range_dist_bc[dir_to_swap, 1] = \ - range_dist_bc[dir_to_swap, 1], range_dist_bc[dir_to_swap, 0] - pr_to_cell_location(range_dist_bc, normal_idx, grid_loc, extr) - - inter = intersect_pr(range_part_bc_g[dirs,:], range_dist_bc[dirs,:]) - if inter is not None: - sub_pr = np.empty((idx_dim,2), dtype=np.int32) - sub_pr[dirs,:] = inter - sub_pr[normal_idx,:] = range_part_bc_g[normal_idx,:] - - #Move back to local numbering, original location and unswapped - pr_to_global_num(sub_pr, p_zone_offset, reverse=True) - pr_to_cell_location(sub_pr, normal_idx, grid_loc, extr, reverse=True) - sub_pr[dir_to_swap, 0], sub_pr[dir_to_swap, 1] = \ - sub_pr[dir_to_swap, 1], sub_pr[dir_to_swap, 0] - - #Effective creation of BC in part zone - if PT.get_label(dist_bc) == 'BCDataSet_t': - path_node = PT.get_child_from_name(dist_bc, '__maia::dspath') - parent_path = PT.get_value(path_node) - PT.rm_child(dist_bc, path_node) #Cleanup - #BC should have been created before so its ok - parent = PT.get_node_from_path(p_zone, parent_path) - part_bc = PT.new_node(PT.get_name(dist_bc), 'BCDataSet_t', parent=parent) - PT.new_IndexRange(value=sub_pr, parent=part_bc) - sub_pr_loc = np.copy(sub_pr) - sub_pr_loc[0,:] += range_part_bc_g[0,0] - range_dist_bc[0,0] - if idx_dim >= 2: - sub_pr_loc[1,:] += range_part_bc_g[1,0] - range_dist_bc[1,0] - if idx_dim >= 3: - sub_pr_loc[2,:] += range_part_bc_g[2,0] - range_dist_bc[2,0] - sub_pr_loc[normal_idx,:] = 1 - - i_ar = np.arange(sub_pr_loc[0,0], sub_pr_loc[0,1]+1, dtype=pdm_dtype) - if idx_dim == 1: - bcds_lntogn = i_ar - else: - j_ar = np.arange(sub_pr_loc[1,0], sub_pr_loc[1,1]+1, dtype=pdm_dtype).reshape(-1,1) - if idx_dim == 3: - k_ar = np.arange(sub_pr_loc[2,0], sub_pr_loc[2,1]+1, dtype=pdm_dtype).reshape(-1,1,1) - else: - k_ar = np.ones(1, dtype=pdm_dtype).reshape(-1,1,1) - bcds_lntogn = s_numbering.ijk_to_index(i_ar, j_ar, k_ar, PT.PointRange.SizePerIndex(dist_bc_pr_n)).flatten() - assert bcds_lntogn.size == PT.Subset.n_elem(part_bc) - MT.newGlobalNumbering({'Index' : bcds_lntogn}, part_bc) - else: #GC are put with bc and treated afterward - part_bc = PT.new_BC(PT.get_name(dist_bc), point_range=sub_pr, parent=zbc) - PT.set_value(part_bc, PT.get_value(dist_bc)) - PT.new_GridLocation(grid_loc, parent=part_bc) - PT.add_child(part_bc, PT.get_child_from_name(dist_bc, 'Transform')) - PT.add_child(part_bc, PT.get_child_from_label(dist_bc, 'GridConnectivityType_t')) - PT.add_child(part_bc, PT.get_child_from_label(dist_bc, 'GridConnectivityProperty_t')) - if PT.get_child_from_name(dist_bc, 'GridConnectivityDonorName') is not None: - PT.add_child(part_bc, PT.get_child_from_name(dist_bc, 'GridConnectivityDonorName')) - PT.new_child(part_bc, 'distPR', 'IndexRange_t', PT.get_child_from_name(dist_bc, 'PointRange')[1]) - PT.new_child(part_bc, 'distPRDonor', 'IndexRange_t', PT.get_child_from_name(dist_bc, 'PointRangeDonor')[1]) - PT.new_child(part_bc, 'zone_offset', 'DataArray_t', p_zone_offset) - if len(PT.get_children(zbc)) == 0: - PT.rm_child(p_zone, zbc) - -def create_internal_gcs(d_zone, p_zones, p_zones_offset, comm): - """ - """ - # 1. Collect : for each partition, select the boundary corresponding to new (internal) - # joins. We store the PR the boundary in (cell) global numbering - idx_dim = PT.Zone.IndexDimension(d_zone) - jn_list = [ [] for i in range(len(p_zones))] - for geo_bnd in ["xmin", "ymin", "zmin", "xmax", "ymax", "zmax"]: - normal_idx = dir_to_idx[geo_bnd[0]] - extr = min_max_as_int(geo_bnd) #0 if min, 1 if max - if normal_idx > idx_dim-1: - continue - for i_part, (p_zone, p_zone_offset) in enumerate(zip(p_zones, p_zones_offset)): - range_part_bc_g = zone_cell_range(p_zone) - range_part_bc_g[normal_idx, 1-extr] = range_part_bc_g[normal_idx, extr] - pr_to_global_num(range_part_bc_g, p_zone_offset) - #Check if part boundary is internal or comes from an old BC or GC - is_old_bc = range_part_bc_g[normal_idx,extr] == zone_cell_range(d_zone)[normal_idx,extr] - if not is_old_bc: - jn_list[i_part].append(PT.new_IndexRange(geo_bnd, range_part_bc_g)) - - # 2. Exchange. We will need to compare to other parts joins - all_offset_list = comm.allgather(p_zones_offset) - all_jn_list = comm.allgather(jn_list) - - grank_l = MT.conv.get_part_suffix(PT.get_name(p_zone))[0] #Rank id in global communicator. - # Normaly procs have at least one partition - grank = comm.allgather(grank_l) # Global rank id of all local ranks, needed for GC naming - - # 3. Process - for i_part, p_zone in enumerate(p_zones): - zgc = PT.new_node('ZoneGridConnectivity', 'ZoneGridConnectivity_t', parent=p_zone) - for jn in jn_list[i_part]: - # Get data for the current join - normal_idx = dir_to_idx[PT.get_name(jn)[0]] - extr = min_max_as_int(PT.get_name(jn)[1:]) - shift = 1 - 2*extr #1 si min, -1 si max - dirs = np.where(np.arange(idx_dim) != normal_idx)[0] - my_pr = PT.get_value(jn) - - # Check opposite joins - for j_proc, opp_parts in enumerate(all_jn_list): - for j_part, opp_part in enumerate(opp_parts): - for opp_jn in opp_part: - opp_normal_idx = dir_to_idx[PT.get_name(opp_jn)[0]] - opp_extr = min_max_as_int(PT.get_name(opp_jn)[1:]) - opp_pr = PT.get_value(opp_jn) - is_admissible = opp_normal_idx == normal_idx \ - and opp_extr != extr \ - and my_pr[normal_idx,0] - shift == opp_pr[normal_idx,0] - if is_admissible: - inter = intersect_pr(my_pr[dirs,:], opp_pr[dirs,:]) - if inter is not None: - sub_pr = np.empty((idx_dim,2), dtype=np.int32) - sub_pr[dirs,:] = inter - sub_pr[normal_idx,:] = my_pr[normal_idx,:] - - sub_pr_d = np.copy(sub_pr) - sub_pr_d[normal_idx] -= shift - #Global to local - pr_to_global_num(sub_pr, p_zones_offset[i_part], reverse=True) - pr_to_global_num(sub_pr_d, all_offset_list[j_proc][j_part], reverse=True) - #Restore location - pr_to_cell_location(sub_pr, normal_idx, 'Vertex', extr, reverse=True) - pr_to_cell_location(sub_pr_d, normal_idx, 'Vertex', 1-extr, reverse=True) - - #Effective creation of GC in part zone - gc_name = MT.conv.name_intra_gc(grank[comm.Get_rank()], i_part, grank[j_proc], j_part) - opp_zone = MT.conv.add_part_suffix(PT.get_name(d_zone), grank[j_proc], j_part) - transform = np.arange(1, idx_dim+1, dtype=np.int32) - part_gc = PT.new_GridConnectivity1to1(gc_name, opp_zone, transform=transform, parent=zgc) - PT.new_IndexRange('PointRange', sub_pr, parent=part_gc) - PT.new_IndexRange('PointRangeDonor', sub_pr_d, parent=part_gc) - -def split_original_joins_S(all_part_zones, comm): - """ - Intersect the original joins of the meshes. Such joins must are stored in ZoneBC_t node - at the end of S partitioning with needed information stored inside - """ - ori_jn_to_pr = dict() - zones_offsets = dict() - for part in all_part_zones: - dzone_name = MT.conv.get_part_prefix(part[0]) - for jn in PT.iter_children_from_predicates(part, 'ZoneBC_t/BC_t'): - if PT.get_child_from_name(jn, 'GridConnectivityDonorName') is not None: - p_zone_offset = PT.get_child_from_name(jn, 'zone_offset')[1] - pr_n = PT.new_IndexRange(part[0], np.copy(PT.get_child_from_name(jn, 'PointRange')[1])) - key = dzone_name + '/' + jn[0] #TODO : Be carefull if multibase ; this key may clash - # Pr dans la num globale de la zone - pr_to_global_num(pr_n[1], p_zone_offset) - try: - ori_jn_to_pr[key].append(pr_n) - except KeyError: - ori_jn_to_pr[key] = [pr_n] - zones_offsets[PT.get_name(part)] = p_zone_offset - - #Gather and create dic jn -> List of PR - ori_jn_to_pr_glob = dict() - all_offset_zones = dict() - for ori_jn_to_pr_rank in comm.allgather(ori_jn_to_pr): - for key, value in ori_jn_to_pr_rank.items(): - if key in ori_jn_to_pr_glob: - ori_jn_to_pr_glob[key].extend(value) - else: - ori_jn_to_pr_glob[key] = value - for zones_offsets_rank in comm.allgather(zones_offsets): - all_offset_zones.update(zones_offsets_rank) - - for part in all_part_zones: - zone_gc = PT.update_child(part, 'ZoneGridConnectivity', 'ZoneGridConnectivity_t') - to_delete = [] - for jn in PT.get_children_from_predicates(part, 'ZoneBC_t/BC_t'): - if PT.get_child_from_name(jn, 'GridConnectivityDonorName') is not None: - dist_pr = PT.get_child_from_name(jn, 'distPR')[1] - dist_prd = PT.get_child_from_name(jn, 'distPRDonor')[1] - PT.set_label(jn, 'GridConnectivity1to1_t') - transform = PT.GridConnectivity.Transform(jn) - T_matrix = PT.GridConnectivity.Transform(jn, True) - idx_dim = transform.size - assert PT.Subset.GridLocation(jn) == 'Vertex' - - #Jn dans la num globale de la dist_zone - p_zone_offset = PT.get_child_from_name(jn, 'zone_offset')[1] - pr = np.copy(PT.get_child_from_name(jn, 'PointRange')[1]) - pr_to_global_num(pr, p_zone_offset) - - #Jn dans la num globale de la dist_zone opposée - pr_in_opp_abs = PT.utils._gc_transform_window(pr, dist_pr[:,0], dist_prd[:,0], T_matrix) - - #Jn dans la zone opposée et en cellules - normal_idx = PT.Subset.normal_axis(PT.new_BC(point_range=pr_in_opp_abs))# Fake subset to enter PT function - dirs = np.where(np.arange(idx_dim) != normal_idx)[0] - bnd_is_max = pr_in_opp_abs[normal_idx,0] != 1 #Sommets - dir_to_swap = (pr_in_opp_abs[:,1] < pr_in_opp_abs[:,0]) - pr_in_opp_abs[dir_to_swap, 0], pr_in_opp_abs[dir_to_swap, 1] = \ - pr_in_opp_abs[dir_to_swap, 1], pr_in_opp_abs[dir_to_swap, 0] - pr_to_cell_location(pr_in_opp_abs, normal_idx, 'Vertex', bnd_is_max) - - opp_jn_key = PT.get_value(jn).split('/')[-1] + '/' + PT.get_value(PT.get_child_from_name(jn, 'GridConnectivityDonorName')) - opposed_joins = ori_jn_to_pr_glob[opp_jn_key] - - to_delete.append(jn) - i_sub_jn = 0 - for opposed_join in opposed_joins: - pr_opp_abs = np.copy(PT.get_value(opposed_join)) - # Also swap opposed jn (using same directions) - pr_opp_abs[dir_to_swap, 0], pr_opp_abs[dir_to_swap, 1] = \ - pr_opp_abs[dir_to_swap, 1], pr_opp_abs[dir_to_swap, 0] - pr_to_cell_location(pr_opp_abs, normal_idx, 'Vertex', bnd_is_max) - inter = intersect_pr(pr_in_opp_abs[dirs,:], pr_opp_abs[dirs,:]) - if inter is not None: - sub_prd = np.empty((idx_dim,2), dtype=np.int32) - sub_prd[dirs,:] = inter - sub_prd[normal_idx,:] = pr_in_opp_abs[normal_idx,:] - # Go back to vertex and invert swap - pr_to_cell_location(sub_prd, normal_idx, 'Vertex', bnd_is_max, reverse=True) - sub_prd[dir_to_swap, 0], sub_prd[dir_to_swap, 1] = \ - sub_prd[dir_to_swap, 1], sub_prd[dir_to_swap, 0] - # Go back to dist_zone - sub_pr = PT.utils._gc_transform_window(sub_prd, dist_prd[:,0], dist_pr[:,0], T_matrix.T) - # Go back to local numbering - pr_to_global_num(sub_pr, p_zone_offset, reverse=True) - p_zone_offset_opp = all_offset_zones[PT.get_name(opposed_join)] - pr_to_global_num(sub_prd, p_zone_offset_opp, reverse=True) - - #Effective creation of GC in part zone - gc_name = PT.get_name(jn) + '.' + str(i_sub_jn) - # Catch opposite base if present - opp_path = PT.get_value(jn) - opp_base = opp_path.split('/')[0] + '/' if '/' in opp_path else '' - opp_zone = PT.get_name(opposed_join) - part_gc = PT.new_GridConnectivity1to1(gc_name, opp_base + opp_zone, transform=transform, parent=zone_gc) - PT.new_IndexRange('PointRange', sub_pr, parent=part_gc) - PT.new_IndexRange('PointRangeDonor', sub_prd, parent=part_gc) - PT.add_child(part_gc, PT.get_child_from_label(jn, 'GridConnectivityProperty_t')) - PT.add_child(part_gc, PT.get_child_from_name(jn, 'GridConnectivityDonorName')) - i_sub_jn += 1 - elif PT.get_child_from_label(jn, 'GridConnectivityType_t') is not None: - #This is a join, but not 1to1. So we just move it with other jns - PT.set_label(jn, 'GridConnectivity_t') - PT.rm_children_from_label(jn, 'GridConnectivityType_t') # Will be added after by post_split - PT.add_child(zone_gc, jn) - to_delete.append(jn) - #Cleanup - zbc = PT.get_child_from_label(part, 'ZoneBC_t') #All jns are stored under ZBC - for node in to_delete: - PT.rm_child(zbc, node) - -def compute_face_gnum(dist_zone_cell_size, cell_window, dtype=pdm_dtype): - dist_cell_per_dir = dist_zone_cell_size - dist_vtx_per_dir = dist_zone_cell_size + 1 - part_cell_per_dir = cell_window[:,1] - cell_window[:,0] - - part_face_per_dir = np.array([(part_cell_per_dir[0]+1)*part_cell_per_dir[1]*part_cell_per_dir[2], - (part_cell_per_dir[1]+1)*part_cell_per_dir[0]*part_cell_per_dir[2], - (part_cell_per_dir[2]+1)*part_cell_per_dir[0]*part_cell_per_dir[1]]) - shifted_nface_p = np_utils.sizes_to_indices(part_face_per_dir) - ijk_to_faceIndex = [s_numbering.ijk_to_faceiIndex, s_numbering.ijk_to_facejIndex, s_numbering.ijk_to_facekIndex] - face_lntogn = np.empty(shifted_nface_p[-1], dtype=dtype) - for idir in range(3): - i_ar = np.arange(cell_window[0,0], cell_window[0,1]+(idir==0), dtype=dtype) - j_ar = np.arange(cell_window[1,0], cell_window[1,1]+(idir==1), dtype=dtype).reshape(-1,1) - k_ar = np.arange(cell_window[2,0], cell_window[2,1]+(idir==2), dtype=dtype).reshape(-1,1,1) - face_lntogn[shifted_nface_p[idir]:shifted_nface_p[idir+1]] = ijk_to_faceIndex[idir](i_ar, j_ar, k_ar, \ - dist_cell_per_dir, dist_vtx_per_dir).flatten() - return face_lntogn - -def create_zone_gnums(cell_window, dist_zone_cell_size, dtype=pdm_dtype): - """ - Create the vertex, face and cell global numbering for a partitioned zone - from the cell_window array, a (3,2) shaped array indicating where starts and ends the - partition cells (semi open, start at 1) and the dist_zone_cell_size (ie number of cells - of the original dist_zone in each direction) - """ - - idx_dim = dist_zone_cell_size.size - dist_cell_per_dir = dist_zone_cell_size - part_cell_per_dir = cell_window[:,1] - cell_window[:,0] - dist_vtx_per_dir = dist_zone_cell_size + 1 - - # Vertex - i_ar = np.arange(cell_window[0,0], cell_window[0,1]+1, dtype=dtype) - if idx_dim == 1: - vtx_lntogn = i_ar - else: - j_ar = np.arange(cell_window[1,0], cell_window[1,1]+1, dtype=dtype).reshape(-1,1) - if idx_dim == 3: - k_ar = np.arange(cell_window[2,0], cell_window[2,1]+1, dtype=dtype).reshape(-1,1,1) - else: - k_ar = np.ones(1, dtype=dtype).reshape(-1,1,1) - vtx_lntogn = s_numbering.ijk_to_index(i_ar, j_ar, k_ar, dist_vtx_per_dir).flatten() - - # Cell - i_ar = np.arange(cell_window[0,0], cell_window[0,1], dtype=dtype) - if idx_dim == 1: - cell_lntogn = i_ar - else: - j_ar = np.arange(cell_window[1,0], cell_window[1,1], dtype=dtype).reshape(-1,1) - if idx_dim == 3: - k_ar = np.arange(cell_window[2,0], cell_window[2,1], dtype=dtype).reshape(-1,1,1) - else: - k_ar = np.ones(1, dtype=dtype).reshape(-1,1,1) - cell_lntogn = s_numbering.ijk_to_index(i_ar, j_ar, k_ar, dist_cell_per_dir).flatten() - - # Faces - if idx_dim == 3: - face_lntogn = compute_face_gnum(dist_zone_cell_size, cell_window, dtype=dtype) - - else: - face_lntogn = None - - return vtx_lntogn, face_lntogn, cell_lntogn - -def part_s_zone(d_zone, d_zone_weights, comm, g_rank): - - i_rank = comm.Get_rank() - n_rank = comm.Get_size() - - n_part_this_zone = np.array(len(d_zone_weights), dtype=np.int32) - n_part_each_proc = np.empty(n_rank, dtype=np.int32) - comm.Allgather(n_part_this_zone, n_part_each_proc) - - my_weights = np.asarray(d_zone_weights, dtype=np.float64) - all_weights = np.empty(n_part_each_proc.sum(), dtype=np.float64) - comm.Allgatherv(my_weights, [all_weights, n_part_each_proc]) - idx_dim = PT.Zone.IndexDimension(d_zone) - dist_cell_size = PT.Zone.CellSize(d_zone) - - if dist_cell_size.size > 1: - all_parts = SCT.split_S_block(dist_cell_size, len(all_weights), all_weights) - else: - all_parts = SCT.split_S_line(dist_cell_size, all_weights) - - my_start = n_part_each_proc[:i_rank].sum() - my_end = my_start + n_part_this_zone - my_parts = all_parts[my_start:my_end] - - part_zones = [] - for i_part, part in enumerate(my_parts): - #Get dim and setup zone - cell_bounds = np.asarray(part, dtype=np.int32) + 1 #Semi open, but start at 1 - n_cells = np.diff(cell_bounds) - # Use the rank id in the global communicator (g_rank) to name the partitioned zone - pzone_name = MT.conv.add_part_suffix(PT.get_name(d_zone), g_rank, i_part) - pzone_dims = np.hstack([n_cells+1, n_cells, np.zeros((idx_dim,1), dtype=np.int32)]) - part_zone = PT.new_Zone(pzone_name, size=pzone_dims, type='Structured') - - entities = ['Vertex', 'Face', 'Cell'] - entities_gnum = create_zone_gnums(cell_bounds, PT.Zone.CellSize(d_zone)) - entities_gnum_dict = {key:val for key,val in zip(entities, entities_gnum) if val is not None} - gn_node = MT.newGlobalNumbering(entities_gnum_dict, parent=part_zone) - _cell_bounds = np.copy(cell_bounds, order='F') - _cell_bounds[:,1] -= 1 - PT.new_node("CellRange", "IndexRange_t", _cell_bounds, parent=gn_node) - PT.new_DataArray("CellSize", PT.Zone.CellSize(d_zone), parent=gn_node) - - create_bcs(d_zone, part_zone, cell_bounds[:,0]) - - part_zones.append(part_zone) - - parts_offset = [np.asarray(part, dtype=np.int32)[:,0] + 1 for part in my_parts] - create_internal_gcs(d_zone, part_zones, parts_offset, comm) - - return part_zones diff --git a/maia/factory/partitioning/split_S/split_cut_tree.py b/maia/factory/partitioning/split_S/split_cut_tree.py deleted file mode 100644 index 88e4724a..00000000 --- a/maia/factory/partitioning/split_S/split_cut_tree.py +++ /dev/null @@ -1,143 +0,0 @@ -import itertools -import numpy as np -from copy import deepcopy -from . import balancing_cut_tree as BCT - -def get_part_volume(part_bounds): - """Compute the number of cells in a structured partition described - by its bounds""" - np_array = np.asarray(part_bounds) - return abs(np.diff(np_array).prod()) - -def apply_weights_to_cut_tree(cut_tree, weights): - """ Apply a list of weights to a cut_tree and return a weigthed tree. - A weigthed tree is a modified cut_tree (see balancing_cut_tree.py) where the number - of leaves in terminal nodes is replaced by the weight(%) of each of these leaves. - Weights are applied on leaves from left to right. - tree = [[[1], [1]], [[2]]] + weights = [.2, .1, .5, .2] -> [[[.2], [.1]], [[.5,.2]]] - Only for 2d/3d trees, not generic - """ - w_tree = deepcopy(cut_tree) - shift = 0 - if BCT.depth(w_tree) == 2: - for i1,child in enumerate(w_tree): - len_node = child[0] - w_tree[i1] = weights[shift:shift+len_node] - shift += len_node - elif BCT.depth(w_tree) == 3: - for i1 in w_tree: - for j, i2 in enumerate(i1): - len_node = i2[0] - i1[j] = weights[shift:shift+len_node] - shift += len_node - return w_tree - -def bct_to_partitions_bounds(tree, dims, w_tree=None): - """ Deduce the partitions bounds from a balancing cuttree and - zone dimensions. - If w_tree is None, all parts are of same volume (n_cell/n_part) - Otherwise, uses w_tree to produce partition of requested size - percentage. - Return a list of partition bounds of size n_part, each bounds beeing - semi open intervals [[istart, iend], [jstart, jend], [kstart, kend]] - starting at 0. - """ - parts = [] - if w_tree is not None: - x_weights = [BCT.sum_leaves(child) / BCT.sum_leaves(w_tree) for child in w_tree] - else: - x_weights = [BCT.sum_leaves(child) / BCT.sum_leaves(tree) for child in tree] - - sumed_weights = [sum(x_weights[:i]) for i in range(len(tree)+1)] - x_splits = [int(weight*dims[0]+0.5) for weight in sumed_weights] - - if len(dims) == 2: - for i, child in enumerate(tree): - n_y_splits = child[0] - if w_tree is not None: - w_sub_tree = w_tree[i] - y_weights = [leave/BCT.sum_leaves(w_sub_tree) for leave in w_sub_tree] - else: - y_weights = n_y_splits * [1. / n_y_splits] - sumed_weights = [sum(y_weights[:i]) for i in range(n_y_splits+1)] - y_splits = [int(weight*dims[1]+0.5) for weight in sumed_weights] - for j in range(n_y_splits): - part = [[x_splits[i], x_splits[i+1]], [y_splits[j], y_splits[j+1]]] - parts.append(part) - else: #Use recursion if 3d - for i, i_child in enumerate(tree): - w_sub_tree = w_tree[i] if w_tree is not None else None - y_splits_this_col = bct_to_partitions_bounds(i_child, dims[1:], w_sub_tree) - for split_2d in y_splits_this_col: - part = [[x_splits[i], x_splits[i+1]]] + split_2d - parts.append(part) - - return parts - -def split_S_block(dims, n_parts, weights = None, max_it = 720): - """ Top level function. Split a structured block of number of cells - dims into n_parts partitions. - If weights is None, all the produced partition are (more or less) of - same volume. - Otherwise, a list of weights of size n_parts and for which sum == 1 - can be passed to the function. - To assure maximal respect of this constraint, the function will try - different permutations of weight list until max_it is reached : the - criterion to select the best permutatation if to minimize the quantity - max(weight - ouput_weight). - """ - cut_tree = BCT.init_cut_tree(len(dims)) - for k in range(n_parts-1): - BCT.refine_cut_tree(cut_tree, dims) - - #If weights are none, just split this tree - if weights is None: - return bct_to_partitions_bounds(cut_tree, dims) - #Otherwise, try different weights combinations - else: - assert(len(weights) == n_parts) - assert(abs(sum(weights) - 1) < 1E-9) - n_cells = np.prod(dims) - - # > Init for loop - permutation_idx = list(range(n_parts)) - n_tries = 0 - best_diff = 1E50 - - for permutation in itertools.permutations(enumerate(weights)): - n_tries += 1 - permuted_weights = [p[1] for p in permutation] - w_tree = apply_weights_to_cut_tree(cut_tree, permuted_weights) - parts = bct_to_partitions_bounds(cut_tree, dims, w_tree) - max_diff = max([get_part_volume(parts[k])/n_cells - permuted_weights[k] - for k in range(n_parts)]) - - if max_diff < best_diff: - best_diff = max_diff - best_weights = permuted_weights - permutation_idx = [p[0] for p in permutation] - if (n_tries >= max_it): - break - w_tree = apply_weights_to_cut_tree(cut_tree, best_weights) - parts = bct_to_partitions_bounds(cut_tree, dims, w_tree) - part_permutation = np.argsort(permutation_idx) - return [parts[k] for k in part_permutation] - - -def split_S_line(dim, weights): - """Simplified version of split_S_block where - we divided a single scalar according to provided weights""" - n_cell = np.floor(weights*dim).astype(int) - remainder = dim - n_cell.sum() - assert 0 <= remainder - it = itertools.cycle(range(n_cell.size)) - while remainder > 0: - n_cell[next(it)] += 1 - remainder -= 1 - all_parts = [] - last = 0 - for nc in n_cell: - all_parts.append([[last, last+nc]]) - last += nc - - return all_parts \ No newline at end of file diff --git a/maia/factory/partitioning/split_S/test/test_balancing_cut_tree.py b/maia/factory/partitioning/split_S/test/test_balancing_cut_tree.py deleted file mode 100644 index 0fa036bd..00000000 --- a/maia/factory/partitioning/split_S/test/test_balancing_cut_tree.py +++ /dev/null @@ -1,81 +0,0 @@ -import pytest -from maia.factory.partitioning.split_S import balancing_cut_tree as BCT - -def test_init_cut_tree(): - assert BCT.init_cut_tree(2) == [[1]] - assert BCT.init_cut_tree(3) == [[[1]]] - -def test_reset_sub_tree(): - tree = [[[2],[3]], [[1],[5],[3]], [[8]]] - BCT.reset_sub_tree(tree[1][2]) - assert tree == [[[2],[3]], [[1],[5],[1]], [[8]]] - BCT.reset_sub_tree(tree[0]) - assert tree == [[[1],[1]], [[1],[5],[1]], [[8]]] - BCT.reset_sub_tree(tree) - assert tree == [[[1],[1]], [[1],[1],[1]], [[1]]] - -def test_depth(): - assert BCT.depth(2) == 0 - assert BCT.depth("oups") == 0 - assert BCT.depth([2,4,6,8]) == 1 - assert BCT.depth([2,4,[6],8]) == 2 - assert BCT.depth([2,4,[[6]],8]) == 3 - assert BCT.depth([2,[4,[6]],8]) == 3 - -def test_sum_leaves(): - tree = [[[2],[3]], [[1],[5],[3]], [[1]]] - assert BCT.sum_leaves([[], []]) == 0 - assert BCT.sum_leaves(tree) == 15 - assert BCT.sum_leaves(tree[1]) == 9 - assert BCT.sum_leaves(tree[1][2]) == 3 - -def test_child_with_least_leaves(): - tree = [[[2],[3]], [[5],[1],[3]], [[1]]] - assert BCT.child_with_least_leaves(tree) is tree[2] - assert BCT.child_with_least_leaves(tree[0]) is tree[0][0] - assert BCT.child_with_least_leaves(tree[1]) is tree[1][1] - assert BCT.child_with_least_leaves(tree[2]) is tree[2][0] - -def test_weight(): - tree = [[[2],[3]], [[5],[1],[3]], [[1]]] - dims = [10, 15, 20] - assert BCT.weight(tree, dims) == 3./dims[0] - assert BCT.weight(tree[0], dims) == 2./dims[1] - assert BCT.weight(tree[1], dims) == 3./dims[1] - assert BCT.weight(tree[2], dims) == 1./dims[1] - assert BCT.weight(tree[1][0], dims) == 5./dims[2] - assert BCT.weight(tree[1][1], dims) == 1./dims[2] - assert BCT.weight(tree[1][2], dims) == 3./dims[2] - assert BCT.weight(tree[2][0], dims) == 1./dims[2] - with pytest.raises(ValueError): - BCT.weight(tree[0][1][0], dims) - -def test_select_insertion(): - tree = [[[2],[3]], [[5],[1],[3]], [[1]]] - assert BCT.select_insertion(tree, tree, [10,15,20]) is tree[2][0] - assert BCT.select_insertion(tree, tree, [100,150,20]) is tree[2] - assert BCT.select_insertion(tree[1], tree[1], [10,15,20]) is tree[1][1] - assert BCT.select_insertion(tree[1], tree[1], [10,150,20]) is tree[1] - -def test_insert_child_at(): - tree = [[[2],[3]], [[5],[1],[3]], [[1]]] - BCT.insert_child_at(tree[1][2], [10,15,20]) - assert tree == [[[2],[3]], [[5],[1],[4]], [[1]]] - BCT.insert_child_at(tree[0], [10,15,20]) #Subnode is rebalanced - assert tree == [[[2],[2], [2]], [[5],[1],[4]], [[1]]] - BCT.insert_child_at(tree, [10,15,20]) #Whole tree is rebalanced - assert tree == [[[2], [2], [1]], [[2], [2], [1]], [[2], [2]], [[2], [2]]] - with pytest.raises(ValueError): - BCT.insert_child_at(tree[3][0][0], [10,15,20]) - -def test_refine_cut_tree(): - tree = [[[2],[3]], [[5],[1],[3]], [[1]]] - BCT.refine_cut_tree(tree, [10,15,20]) - assert tree == [[[2], [3]], [[5], [1], [3]], [[2]]] - BCT.refine_cut_tree(tree, [10,15,20]) - assert tree == [[[2], [3]], [[5], [1], [3]], [[2], [1]]] - n_leaves_current = BCT.sum_leaves(tree) - for i in range(5): - BCT.refine_cut_tree(tree, [10,15,20]) - assert BCT.sum_leaves(tree) == n_leaves_current + 5 - diff --git a/maia/factory/partitioning/split_S/test/test_part_zone.py b/maia/factory/partitioning/split_S/test/test_part_zone.py deleted file mode 100644 index 38b9bac3..00000000 --- a/maia/factory/partitioning/split_S/test/test_part_zone.py +++ /dev/null @@ -1,190 +0,0 @@ -import pytest_parallel -import numpy as np -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory.partitioning.split_S import part_zone as splitS - -def test_collect_S_bnd_per_dir(): - yt = """ -Zone Zone_t: - ZBC ZoneBC_t: - bc1 BC_t: - PointRange IndexRange_t [[1,1],[1,8],[1,6]]: - GridLocation GridLocation_t "IFaceCenter": - bc2 BC_t: - PointRange IndexRange_t [[1,9],[1,1],[1,7]]: - GridLocation GridLocation_t "Vertex": - bc8 BC_t: - PointRange IndexRange_t [[9,17],[1,1],[1,7]]: - GridLocation GridLocation_t "Vertex": - bc3 BC_t: - PointRange IndexRange_t [[1,16],[9,9],[1,6]]: - GridLocation GridLocation_t "JFaceCenter": - bc4 BC_t: - PointRange IndexRange_t [[1,16],[1,8],[1,1]]: - GridLocation GridLocation_t "CellCenter": - bc5 BC_t: - PointRange IndexRange_t [[1,16],[1,8],[6,6]]: - GridLocation GridLocation_t "CellCenter": - bc6 BC_t: - PointRange IndexRange_t [[17,17],[1,3],[1,7]]: - bc7 BC_t: - PointRange IndexRange_t [[17,17],[3,8],[5,6]]: - GridLocation GridLocation_t "IFaceCenter": - bc10 BC_t: - PointList IndexArray_t [[1,1,1,1,1],[1,1,1,2,2],[1,2,3,1,2]]: - GridLocation GridLocation_t "IFaceCenter": - ZGC ZoneGridConnectivity_t: - gc1 GridConnectivity1to1_t: - PointRange IndexRange_t [[17,17],[3,9],[1,5]]: -""" - zone = parse_yaml_cgns.to_node(yt) - - out = splitS.collect_S_bnd_per_dir(zone) - assert out["xmin"] == [PT.get_node_from_name(zone, name) for name in ['bc1']] - assert out["ymin"] == [PT.get_node_from_name(zone, name) for name in ['bc2', 'bc8']] - assert out["zmin"] == [PT.get_node_from_name(zone, name) for name in ['bc4']] - assert out["xmax"] == [PT.get_node_from_name(zone, name) for name in ['bc6', 'bc7', 'gc1']] - assert out["ymax"] == [PT.get_node_from_name(zone, name) for name in ['bc3']] - assert out["zmax"] == [PT.get_node_from_name(zone, name) for name in ['bc5']] - -def test_intersect_pr(): - assert splitS.intersect_pr(np.array([[7,11],[1,5]]), np.array([[12,16],[1,5]])) is None - assert (splitS.intersect_pr(np.array([[7,11],[1,5]]), np.array([[7,11],[1,5]])) \ - == np.array([[7,11],[1,5]])).all() - assert (splitS.intersect_pr(np.array([[1,5],[1,3]]), np.array([[1,4],[3,6]])) \ - == np.array([[1,4],[3,3]])).all() - assert (splitS.intersect_pr(np.array([[1,2]]), np.array([[1,4]])) == np.array([[1,2]])).all() - assert splitS.intersect_pr(np.array([[1,2]]), np.array([[5,8]])) is None - assert (splitS.intersect_pr(np.array([[1,2]]), np.array([[2,4]])) == np.array([[2,2]])).all() - -def test_zone_cell_range(): - zone = PT.new_Zone(type='Structured', size=[[101,100,0],[101,100,0],[41,40,0]]) - assert (splitS.zone_cell_range(zone) == np.array([[1,100],[1,100],[1,40]])).all() - -def test_pr_to_cell_location(): - pr = np.array([[1,1],[1,8],[1,6]]) - splitS.pr_to_cell_location(pr, 0, 'IFaceCenter', False) - assert (pr == np.array([[1,1],[1,8],[1,6]])).all() - pr = np.array([[10,10],[1,8],[1,6]]) - splitS.pr_to_cell_location(pr, 0, 'IFaceCenter', True) - assert (pr == np.array([[9,9],[1,8],[1,6]])).all() - splitS.pr_to_cell_location(pr, 0, 'IFaceCenter', True, reverse=True) - assert (pr == np.array([[10,10],[1,8],[1,6]])).all() - - pr = np.array([[11,11],[1,9],[1,7]]) - splitS.pr_to_cell_location(pr, 0, 'Vertex', True) - assert (pr == np.array([[10,10],[1,8],[1,6]])).all() - splitS.pr_to_cell_location(pr, 0, 'Vertex', True, reverse=True) - assert (pr == np.array([[11,11],[1,9],[1,7]])).all() - -def test_pr_to_global_num(): - pr = np.array([[11,11],[1,9],[1,7]]) - splitS.pr_to_global_num(pr, np.array([10,1,100]), reverse=False) - assert (pr == np.array([[11+9,11+9],[1,9],[1+99,7+99]])).all() - splitS.pr_to_global_num(pr, np.array([10,1,100]), reverse=True) - assert (pr == np.array([[11,11],[1,9],[1,7]])).all() - -@pytest_parallel.mark.parallel(3) -def test_split_original_joins_S(comm): - if comm.Get_rank() == 0: - pt = """ -Big.P0.N0 Zone_t: -Small.P0.N0 Zone_t: - """ - elif comm.Get_rank() == 1: - pt = """ -Small.P1.N0 Zone_t: - ZBC ZoneBC_t: - match2 BC_t "Big": - GridConnectivityDonorName Descriptor_t "match1": - Transform int[IndexDimension] [-2,-1,-3]: - PointRange IndexRange_t [[4,1],[4,4],[5,1]]: - distPR IndexRange_t [[7,1],[9,9],[5,1]]: - distPRDonor IndexRange_t [[17,17],[3,9],[1,5]]: - zone_offset DataArray_t [1,6,1]: - """ - elif comm.Get_rank() == 2: - pt = """ -Big.P2.N0 Zone_t: - ZBC ZoneBC_t: - match1 BC_t "Small": - GridConnectivityDonorName Descriptor_t "match2": - Transform int[IndexDimension] [-2,-1,-3]: - PointRange IndexRange_t [[6,6],[3,5],[1,5]]: - distPR IndexRange_t [[17,17],[3,9],[1,5]]: - distPRDonor IndexRange_t [[7,1],[9,9],[5,1]]: - zone_offset DataArray_t [12,1,1]: -Big.P2.N1 Zone_t: - ZBC ZoneBC_t: - match1 BC_t "Small": - GridConnectivityDonorName Descriptor_t "match2": - Transform int[IndexDimension] [-2,-1,-3]: - PointRange IndexRange_t [[6,6],[1,5],[1,5]]: - distPR IndexRange_t [[17,17],[3,9],[1,5]]: - distPRDonor IndexRange_t [[7,1],[9,9],[5,1]]: - zone_offset DataArray_t [12,5,1]: -Small.P2.N1 Zone_t: - ZBC ZoneBC_t: - match2 BC_t "Big": - GridConnectivityDonorName Descriptor_t "match1": - Transform int[IndexDimension] [-2,-1,-3]: - PointRange IndexRange_t [[4,1],[4,4],[5,1]]: - distPR IndexRange_t [[7,1],[9,9],[5,1]]: - distPRDonor IndexRange_t [[17,17],[3,9],[1,5]]: - zone_offset DataArray_t [4,6,1]: - """ - part_tree = parse_yaml_cgns.to_cgns_tree(pt) - part_zones = PT.get_all_Zone_t(part_tree) - splitS.split_original_joins_S(part_zones, comm) - - if comm.Get_rank() == 0: - assert PT.get_nodes_from_name(part_tree, 'match1.*') == [] - assert PT.get_nodes_from_name(part_tree, 'match2.*') == [] - elif comm.Get_rank() == 1: - assert PT.get_nodes_from_name(part_tree, 'match1.*') == [] - assert len(PT.get_nodes_from_name(part_tree, 'match2.*')) == 1 - match2 = PT.get_node_from_name(part_tree, 'match2.0') - assert PT.get_value(match2) == 'Big.P2.N1' - assert PT.get_label(match2) == 'GridConnectivity1to1_t' - assert (PT.get_node_from_name(match2, 'PointRange')[1] == [[4,1],[4,4],[5,1]]).all() - assert (PT.get_node_from_name(match2, 'PointRangeDonor')[1] == [[6,6],[2,5],[1,5]]).all() - elif comm.Get_rank() == 2: - assert len(PT.get_nodes_from_name(part_tree, 'match1.*')) == 3 - assert len(PT.get_nodes_from_name(part_tree, 'match2.*')) == 2 - match1_1 = PT.get_node_from_path(part_tree, 'Base/Big.P2.N0/ZoneGridConnectivity/match1.0') - assert PT.get_value(match1_1) == 'Small.P2.N1' - assert (PT.get_node_from_name(match1_1, 'PointRange')[1] == [[6,6],[3,5],[1,5]]).all() - assert (PT.get_node_from_name(match1_1, 'PointRangeDonor')[1] == [[4,2],[4,4],[5,1]]).all() - match1_2 = PT.get_node_from_path(part_tree, 'Base/Big.P2.N1/ZoneGridConnectivity/match1.0') - assert PT.get_value(match1_2) == 'Small.P1.N0' - assert (PT.get_node_from_name(match1_2, 'PointRange')[1] == [[6,6],[2,5],[1,5]]).all() - assert (PT.get_node_from_name(match1_2, 'PointRangeDonor')[1] == [[4,1],[4,4],[5,1]]).all() - match1_3 = PT.get_node_from_path(part_tree, 'Base/Big.P2.N1/ZoneGridConnectivity/match1.1') - assert PT.get_value(match1_3) == 'Small.P2.N1' - assert (PT.get_node_from_name(match1_3, 'PointRange')[1] == [[6,6],[1,2],[1,5]]).all() - assert (PT.get_node_from_name(match1_3, 'PointRangeDonor')[1] == [[2,1],[4,4],[5,1]]).all() - match2_1 = PT.get_node_from_path(part_tree, 'Base/Small.P2.N1/ZoneGridConnectivity/match2.0') - assert PT.get_value(match2_1) == 'Big.P2.N0' - assert (PT.get_node_from_name(match2_1, 'PointRange')[1] == [[4,2],[4,4],[5,1]]).all() - assert (PT.get_node_from_name(match2_1, 'PointRangeDonor')[1] == [[6,6],[3,5],[1,5]]).all() - match2_2 = PT.get_node_from_path(part_tree, 'Base/Small.P2.N1/ZoneGridConnectivity/match2.1') - assert PT.get_value(match2_2) == 'Big.P2.N1' - assert (PT.get_node_from_name(match2_2, 'PointRange')[1] == [[2,1],[4,4],[5,1]]).all() - assert (PT.get_node_from_name(match2_2, 'PointRangeDonor')[1] == [[6,6],[1,2],[1,5]]).all() - -def test_create_zone_gnums(): - dist_zone_cell = np.array([6,8,4]) - cell_window = np.array([[4,6], [6,8], [1,3]]) - vtx_gnum, face_gnum, cell_gnum = splitS.create_zone_gnums(cell_window, dist_zone_cell, dtype=np.int32) - - expected_vtx = np.array([39,40,41,46,47,48,53,54,55,102,103,104,109,110,111,116,117,118, - 165,166,167,172,173,174,179,180,181]) - expected_face = np.array([ 39 ,40 ,41 ,46 ,47 ,48 ,95 ,96 ,97 ,102,103,104,258,259,264,265,270,271, - 312,313,318,319,324,325,474,475,480,481,522,523,528,529,570,571,576,577]) - expected_cell = np.array([34,35,40,41,82,83,88,89]) - - assert (vtx_gnum == expected_vtx ).all() - assert (face_gnum == expected_face).all() - assert (cell_gnum == expected_cell).all() - assert vtx_gnum.dtype == face_gnum.dtype == cell_gnum.dtype == np.int32 diff --git a/maia/factory/partitioning/split_S/test/test_split_cut_tree.py b/maia/factory/partitioning/split_S/test/test_split_cut_tree.py deleted file mode 100644 index 18c30be8..00000000 --- a/maia/factory/partitioning/split_S/test/test_split_cut_tree.py +++ /dev/null @@ -1,137 +0,0 @@ -import pytest -import numpy as np -from maia.factory.partitioning.split_S import split_cut_tree as SCT - -def test_part_volume(): - assert SCT.get_part_volume([[0, 7], [0, 5]]) == 5*7 - assert SCT.get_part_volume([[2, 7], [1, 5]]) == 5*4 - assert SCT.get_part_volume([[2, 7], [1, 1]]) == 0 - assert SCT.get_part_volume([[5, 10], [2, 10], [0,4]]) == 5*8*4 - -def test_apply_weights_to_cut_tree(): - assert SCT.apply_weights_to_cut_tree([[2], [1], [1]], [.1, .2, .3, .4]) \ - == [[.1, .2], [.3], [.4]] - assert SCT.apply_weights_to_cut_tree([[[1], [1]], [[2]]], [.2, .1, .5, .2]) \ - == [[[.2], [.1]], [[.5,.2]]] - -class Test_bct_to_partitions_bounds(): - def test_2d_unweighted(self): - parts = SCT.bct_to_partitions_bounds([[2], [1]], [10,10]) - assert len(parts) == 3 - assert parts[0] == [[0, 7], [0, 5]] - assert parts[1] == [[0, 7], [5, 10]] - assert parts[2] == [[7, 10], [0, 10]] - - parts = SCT.bct_to_partitions_bounds([[1], [1], [1]], [30,10]) - assert len(parts) == 3 - assert parts[0] == [[0, 10], [0, 10]] - assert parts[1] == [[10, 20], [0, 10]] - assert parts[2] == [[20, 30], [0, 10]] - - def test_2d_weighted(self): - parts = SCT.bct_to_partitions_bounds([[1], [2]], [10,10], [[.5],[.25,.25]]) - assert len(parts) == 3 - assert parts[0] == [[0, 5], [0, 10]] - assert parts[1] == [[5, 10], [0, 5]] - assert parts[2] == [[5, 10], [5, 10]] - - def test_3d_unweighted(self): - tree = [[[1], [1]], [[2]]] - dims = [10, 10, 10] - parts = SCT.bct_to_partitions_bounds(tree, dims) - assert len(parts) == 4 - assert parts[0] == [[0, 5], [0, 5], [0,10]] - assert parts[1] == [[0, 5], [5, 10], [0,10]] - assert parts[2] == [[5, 10], [0, 10], [0,5]] - assert parts[3] == [[5, 10], [0, 10], [5,10]] - - tree = [[[1]], [[1]], [[1]], [[1]]] - dims = [30, 10, 5] - parts = SCT.bct_to_partitions_bounds(tree, dims) - assert len(parts) == 4 - assert parts[0] == [[0, 8], [0, 10], [0,5]] - assert parts[1] == [[8, 15], [0, 10], [0,5]] - assert parts[2] == [[15, 23], [0, 10], [0,5]] - assert parts[3] == [[23, 30], [0, 10], [0,5]] - - def test_3d_unweighted(self): - tree = [[[1], [1]], [[2]]] - wtree = [[[.2], [.1]], [[.5,.2]]] - dims = [10, 10, 10] - parts = SCT.bct_to_partitions_bounds(tree, dims, wtree) - assert len(parts) == 4 - assert parts[0] == [[0, 3], [0, 7], [0,10]] - assert parts[1] == [[0, 3], [7, 10], [0,10]] - assert parts[2] == [[3, 10], [0, 10], [0,7]] - assert parts[3] == [[3, 10], [0, 10], [7,10]] - -class Test_part_block_from_dims(): - def test_unweighted_2d(self): - parts = SCT.split_S_block([10,10], 4) - assert parts[0] == [[0, 5], [0, 5]] - assert parts[1] == [[0, 5], [5, 10]] - assert parts[2] == [[5, 10], [0, 5]] - assert parts[3] == [[5, 10], [5, 10]] - - parts = SCT.split_S_block([20,15], 3) - assert parts[0] == [[0, 13], [0, 8]] - assert parts[1] == [[0, 13], [8, 15]] - assert parts[2] == [[13, 20], [0, 15]] - - parts = SCT.split_S_block([100,2], 3) - assert parts[0] == [[0, 33], [0, 2]] - assert parts[1] == [[33, 67], [0, 2]] - assert parts[2] == [[67, 100], [0, 2]] - - def test_weighted_2d(self): - parts = SCT.split_S_block([20,15], 3, [.2, .3, .5]) - assert parts[0] == [[0, 10], [0, 6]] - assert parts[1] == [[0, 10], [6, 15]] - assert parts[2] == [[10, 20], [0, 15]] - parts = SCT.split_S_block([20,15], 3, [.2, .5, .3]) - assert parts[0] == [[0, 10], [0, 6]] - assert parts[1] == [[10, 20], [0, 15]] - assert parts[2] == [[0, 10], [6, 15]] - - def test_unweighted_3d(self): - parts = SCT.split_S_block([50, 43, 26], 6) - assert parts[0] == [[0, 25], [0, 29], [0, 13]] - assert parts[1] == [[0, 25], [0, 29], [13, 26]] - assert parts[2] == [[0, 25], [29, 43], [0, 26]] - assert parts[3] == [[25, 50], [0, 29], [0, 13]] - assert parts[4] == [[25, 50], [0, 29], [13, 26]] - assert parts[5] == [[25, 50], [29, 43], [0, 26]] - - assert len(SCT.split_S_block([50, 43, 26], 13)) == 13 - - def test_weighted_3d(self): - weights = [.2, .15, .15, .3, .1, .1] - parts = SCT.split_S_block([50, 43, 26], 6, weights) - assert parts[0] == [[0, 25], [0, 30], [0, 15]] - assert parts[1] == [[0, 25], [0, 30], [15, 26]] - assert parts[2] == [[0, 25], [30, 43], [0, 26]] - assert parts[3] == [[25, 50], [17, 43], [0, 26]] - assert parts[4] == [[25, 50], [0, 17], [0, 13]] - assert parts[5] == [[25, 50], [0, 17], [13, 26]] - error1 = [SCT.get_part_volume(part) / (50*43*26) - w \ - for part,w in zip(parts,weights)] - - parts = SCT.split_S_block([50, 43, 26], 6, weights,max_it=1) - assert parts[0] == [[0, 25], [0, 30], [0, 15]] - assert parts[1] == [[0, 25], [0, 30], [15, 26]] - assert parts[2] == [[0, 25], [30, 43], [0, 26]] - assert parts[3] == [[25, 50], [0, 34], [0, 19]] - assert parts[4] == [[25, 50], [0, 34], [19, 26]] - assert parts[5] == [[25, 50], [34, 43], [0, 26]] - error2 = [SCT.get_part_volume(part) / (50*43*26) - w \ - for part,w in zip(parts,weights)] - - assert max(error1) <= max(error2) - -def test_split_S_line(): - assert SCT.split_S_line(570, np.array([1])) == [[[0,570]]] - assert SCT.split_S_line(100, np.array([.3, .6, .1])) == [[[0,30]], [[30, 90]], [[90, 100]]] - assert SCT.split_S_line(1000, np.array([.38, .6])) == [[[0,390]], [[390, 1000]]] - - with pytest.raises(AssertionError): - SCT.split_S_line(10, np.array([.8, .8])) \ No newline at end of file diff --git a/maia/factory/partitioning/split_U/cgns_to_pdm_dmesh.py b/maia/factory/partitioning/split_U/cgns_to_pdm_dmesh.py deleted file mode 100644 index 8dc8cae6..00000000 --- a/maia/factory/partitioning/split_U/cgns_to_pdm_dmesh.py +++ /dev/null @@ -1,242 +0,0 @@ -import numpy as np -from mpi4py import MPI - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import py_utils, np_utils, layouts, as_pdm_gnum -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.transfer.dist_to_part.index_exchange import collect_distributed_pl - -from Pypdm.Pypdm import DistributedMesh, DistributedMeshNodal -from Pypdm.Pypdm import _PDM_CONNECTIVITY_TYPE_FACE_VTX, _PDM_BOUND_TYPE_FACE, \ - _PDM_CONNECTIVITY_TYPE_FACE_CELL, _PDM_CONNECTIVITY_TYPE_CELL_FACE - -def _split_point_list_by_dim(pl_list, range_by_dim, comm): - """ - Split a list of PointList nodes into 4 sublists depending on the dimension - of each PointList. - Dimension is recovered using the values of the PointList and the range_by_dim - array (GridLocation may be a better choice ?) - """ - def _get_dim(pl): - min_l_pl = np.amin(pl[0,:], initial=np.iinfo(pl.dtype).max) - max_l_pl = np.amax(pl[0,:], initial=-1) - - min_pl = comm.allreduce(min_l_pl, op=MPI.MIN) - max_pl = comm.allreduce(max_l_pl, op=MPI.MAX) - for i_dim in range(len(range_by_dim)): - if(min_pl >= range_by_dim[i_dim][0] and max_pl <= range_by_dim[i_dim][1]): - return i_dim - - return py_utils.bucket_split(pl_list, lambda pl: _get_dim(pl), size=4) - -def cgns_dist_zone_to_pdm_dmesh_vtx(dist_zone, comm): - """ - Create a pdm_dmesh structure for distributed having only vertices - """ - distrib_vtx = PT.get_value(MT.getDistribution(dist_zone, 'Vertex')) - dn_vtx = distrib_vtx[1] - distrib_vtx[0] - - if dn_vtx > 0: - cx, cy, cz = PT.Zone.coordinates(dist_zone) - dvtx_coord = np_utils.interweave_arrays([cx,cy,cz]) - else: - dvtx_coord = np.empty(0, dtype='float64', order='F') - - dmesh = DistributedMesh(comm, 0, 0, 0, dn_vtx) - - dmesh.dmesh_vtx_coord_set(dvtx_coord) - - # > Create an older --> To Suppress after all - multi_part_node = PT.update_child(dist_zone, ':CGNS#MultiPart', 'UserDefinedData_t') - PT.new_DataArray('dvtx_coord', dvtx_coord, parent=multi_part_node) - - return dmesh - -def cgns_dist_zone_to_pdm_dmesh(dist_zone, comm): - """ - Create a pdm_dmesh structure from a distributed zone - """ - distrib_vtx = PT.get_value(MT.getDistribution(dist_zone, 'Vertex')) - distrib_cell = PT.get_value(MT.getDistribution(dist_zone, 'Cell')) - - # > Try to hook NGon - ngon_node = PT.Zone.NGonNode(dist_zone) - ngon_first = PT.Element.Range(ngon_node)[0] == 1 - has_nface = PT.Zone.has_nface_elements(dist_zone) - has_pe = PT.get_node_from_name(ngon_node, 'ParentElements') is not None - dface_vtx = as_pdm_gnum(PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1]) - ngon_eso = PT.get_child_from_name(ngon_node, 'ElementStartOffset' )[1] - - if has_nface: - nface_node = PT.Zone.NFaceNode(dist_zone) - nface_ec = as_pdm_gnum(PT.get_child_from_name(nface_node, 'ElementConnectivity')[1]) - nface_eso = PT.get_child_from_name(nface_node, 'ElementStartOffset' )[1] - distrib_cell_face = as_pdm_gnum(PT.get_value(MT.getDistribution(nface_node, 'ElementConnectivity'))) - if has_pe: - ngon_pe = as_pdm_gnum(PT.get_child_from_name(ngon_node, 'ParentElements')[1]) - - distrib_face = as_pdm_gnum(PT.get_value(MT.getDistribution(ngon_node, 'Element'))) - distrib_face_vtx = as_pdm_gnum(PT.get_value(MT.getDistribution(ngon_node, 'ElementConnectivity'))) - - dn_vtx = distrib_vtx [1] - distrib_vtx [0] - dn_cell = distrib_cell[1] - distrib_cell[0] - dn_face = distrib_face[1] - distrib_face[0] - dn_edge = -1 #Not used - - cx, cy, cz = PT.Zone.coordinates(dist_zone) - dvtx_coord = np_utils.interweave_arrays([cx,cy,cz]) - - dface_vtx_idx = np.add(ngon_eso, -distrib_face_vtx[0], dtype=np.int32) #Local index is int32bits - - if has_nface: #Use NFace to set cell_face - dcell_face_idx = np.add(nface_eso, -distrib_cell_face[0], dtype=np.int32) # Local index is int32bits - if ngon_first: - dcell_face = nface_ec - else: - dcell_face = nface_ec - distrib_cell[2] - if has_pe: #Use PE to set face_cell - dface_cell = np.empty(2*dn_face, dtype=pdm_gnum_dtype) # Respect pdm_gnum_type - layouts.pe_cgns_to_pdm_face_cell(ngon_pe, dface_cell) - if ngon_first: - np_utils.shift_nonzeros(dface_cell, -distrib_face[2]) - - - # > Prepare bnd - dface_bound_idx = np.zeros(1, dtype=np.int32) - dface_bound = np.empty(0, dtype=pdm_gnum_dtype) - - dmesh = DistributedMesh(comm, dn_cell, dn_face, dn_edge, dn_vtx) - - dmesh.dmesh_vtx_coord_set(dvtx_coord) - dmesh.dmesh_connectivity_set(_PDM_CONNECTIVITY_TYPE_FACE_VTX, dface_vtx_idx, dface_vtx) - dmesh.dmesh_bound_set(_PDM_BOUND_TYPE_FACE, dface_bound_idx, dface_bound) - - if has_nface: - dmesh.dmesh_connectivity_set(_PDM_CONNECTIVITY_TYPE_CELL_FACE, dcell_face_idx, dcell_face) - if has_pe: - dmesh.dmesh_connectivity_set(_PDM_CONNECTIVITY_TYPE_FACE_CELL, None, dface_cell) - - # > Create an older --> To Suppress after all - multi_part_node = PT.update_child(dist_zone, ':CGNS#MultiPart', 'UserDefinedData_t') - PT.new_DataArray('dvtx_coord' , dvtx_coord , parent=multi_part_node) - PT.new_DataArray('dface_vtx_idx' , dface_vtx_idx , parent=multi_part_node) - PT.new_DataArray('dface_vtx' , dface_vtx , parent=multi_part_node) - PT.new_DataArray('dface_bound_idx', dface_bound_idx, parent=multi_part_node) - PT.new_DataArray('dface_bound' , dface_bound , parent=multi_part_node) - if has_nface: - PT.new_DataArray('dcell_face_idx', dcell_face_idx, parent=multi_part_node) - PT.new_DataArray('dcell_face' , dcell_face , parent=multi_part_node) - if has_pe: - PT.new_DataArray('dface_cell' , dface_cell , parent=multi_part_node) - - return dmesh - -def cgns_dist_zone_to_pdm_dmesh_poly2d(dist_zone, comm): - distrib_vtx = PT.get_value(MT.getDistribution(dist_zone, 'Vertex')) - distrib_face = PT.get_value(MT.getDistribution(dist_zone, 'Cell')) #In 2d, cell == face - n_vtx = distrib_vtx[2] - n_face = distrib_face[2] - dn_vtx = distrib_vtx[1] - distrib_vtx[0] - dn_face = distrib_face[1] - distrib_face[0] - - #Create DMeshNodal - dmesh_nodal = DistributedMeshNodal(comm, n_vtx, 0, n_face, 0, mesh_dimension=2) - - if dn_vtx > 0: - cx, cy, cz = PT.Zone.coordinates(dist_zone) - dvtx_coord = np_utils.interweave_arrays([cx,cy,cz]) - else: - dvtx_coord = np.empty(0, dtype='float64', order='F') - dmesh_nodal.set_coordinates(dvtx_coord) - - ngon_node = PT.Zone.NGonNode(dist_zone) - ngon_eso = PT.get_child_from_name(ngon_node, 'ElementStartOffset')[1] - ngon_ec = PT.get_child_from_name(ngon_node, 'ElementConnectivity')[1] - - ngon_distri = MT.getDistribution(ngon_node, 'ElementConnectivity')[1] - - dface_vtx_idx = np.add(ngon_eso, -ngon_distri[0], dtype=np.int32) #Local index is int32bits - dface_vtx = as_pdm_gnum(ngon_ec) - dmesh_nodal.set_poly2d_section(dface_vtx_idx, dface_vtx) - - # keep dvtx_coord object alive for ParaDiGM - multi_part_node = PT.update_child(dist_zone, ':CGNS#MultiPart', 'UserDefinedData_t') - PT.new_DataArray('dvtx_coord', dvtx_coord, parent=multi_part_node) - PT.new_DataArray('dface_vtx_idx', dface_vtx_idx, parent=multi_part_node) - PT.new_DataArray('dface_vtx', dface_vtx, parent=multi_part_node) - - return dmesh_nodal - -def cgns_dist_zone_to_pdm_dmesh_nodal(dist_zone, comm, needs_vertex=True, needs_bc=True): - """ - Create a pdm_dmesh_nodal structure from a distributed zone - """ - distrib_vtx = PT.get_value(MT.getDistribution(dist_zone, 'Vertex')) - n_vtx = distrib_vtx[2] - dn_vtx = distrib_vtx[1] - distrib_vtx[0] - - n_elt_per_dim = [0,0,0] - sorted_elts_by_dim = PT.Zone.get_ordered_elements_per_dim(dist_zone) - for elt_dim in sorted_elts_by_dim: - for elt in elt_dim: - assert PT.Element.CGNSName(elt) not in ["NGON_n", "NFACE_n"] - if PT.Element.Dimension(elt) > 0: - n_elt_per_dim[PT.Element.Dimension(elt)-1] += PT.Element.Size(elt) - - #Create DMeshNodal - mesh_dimension = 3 - for n_elt_dim in n_elt_per_dim[::-1]: - if n_elt_dim != 0: - break - mesh_dimension -= 1 - dmesh_nodal = DistributedMeshNodal(comm, n_vtx, *n_elt_per_dim[::-1], mesh_dimension) - - #Vertices - if needs_vertex: - if dn_vtx > 0: - cx, cy, cz = PT.Zone.coordinates(dist_zone) - dvtx_coord = np_utils.interweave_arrays([cx,cy,cz]) - else: - dvtx_coord = np.empty(0, dtype='float64', order='F') - dmesh_nodal.set_coordinates(dvtx_coord) - - # keep dvtx_coord object alive for ParaDiGM - multi_part_node = PT.update_child(dist_zone, ':CGNS#MultiPart', 'UserDefinedData_t') - PT.new_DataArray('dvtx_coord', dvtx_coord, parent=multi_part_node) - - #Elements - to_elmt_size = lambda e : PT.get_value(MT.getDistribution(e, 'Element'))[1] - PT.get_value(MT.getDistribution(e, 'Element'))[0] - - for i_dim, elts in enumerate(sorted_elts_by_dim): - elt_pdm_types = np.array([MT.pdm_elts.element_pdm_type(PT.Element.Type(e)) for e in elts], dtype=np.int32) - elt_lengths = np.array([to_elmt_size(e) for e in elts], dtype=np.int32) - elmts_connectivities = [as_pdm_gnum(PT.get_child_from_name(e, "ElementConnectivity")[1]) for e in elts] - dmesh_nodal.set_sections(MT.pdm_elts.elements_dim_to_pdm_kind[i_dim], elmts_connectivities, elt_pdm_types, elt_lengths) - - # Boundaries - if needs_bc: - range_by_dim = PT.Zone.get_elt_range_per_dim(dist_zone) - - # Skip Vertex-located BCs, because they are not in Element numbering - elts_loc = ['EdgeCenter', 'FaceCenter', 'CellCenter'] - bc_point_lists = collect_distributed_pl(dist_zone, [['ZoneBC_t', 'BC_t']], filter_loc=elts_loc) - # Find out in which dim the boundary refers - bc_point_lists_by_dim = _split_point_list_by_dim(bc_point_lists, range_by_dim, comm) - - for i_dim, bc_pl in enumerate(bc_point_lists_by_dim): - if(len(bc_pl) > 0 ): - delmt_bound_idx, delmt_bound = np_utils.concatenate_point_list(bc_pl, pdm_gnum_dtype) - # Shift because CGNS global numbering is for all elements / ParaDiGM is by dimension - delmt_bound -= (range_by_dim[i_dim][0] - 1) - n_elmt_group = delmt_bound_idx.shape[0] - 1 - #Need an holder to prevent memory deletion - pdm_node = PT.update_child(dist_zone, ':CGNS#DMeshNodal#Bnd{0}'.format(i_dim), 'UserDefinedData_t') - PT.new_DataArray('delmt_bound_idx', delmt_bound_idx, parent=pdm_node) - PT.new_DataArray('delmt_bound' , delmt_bound , parent=pdm_node) - - dmesh_nodal.set_group_elmt(MT.pdm_elts.elements_dim_to_pdm_kind[i_dim], n_elmt_group, delmt_bound_idx, delmt_bound) - - return dmesh_nodal - diff --git a/maia/factory/partitioning/split_U/part_all_zones.py b/maia/factory/partitioning/split_U/part_all_zones.py deleted file mode 100644 index 270bba2a..00000000 --- a/maia/factory/partitioning/split_U/part_all_zones.py +++ /dev/null @@ -1,257 +0,0 @@ -import numpy as np -import Pypdm.Pypdm as PDM - -import maia.pytree as PT - -from . import cgns_to_pdm_dmesh -from .pdm_part_to_cgns_zone import pdm_part_to_cgns_zone - -from maia.utils import py_utils - -maia_to_pdm_entity = {"cell" : PDM._PDM_MESH_ENTITY_CELL, - "face" : PDM._PDM_MESH_ENTITY_FACE, - "edge" : PDM._PDM_MESH_ENTITY_EDGE, - "vtx" : PDM._PDM_MESH_ENTITY_VTX} - -maia_to_pdm_split_tool = {'parmetis' : PDM._PDM_SPLIT_DUAL_WITH_PARMETIS, - 'ptscotch' : PDM._PDM_SPLIT_DUAL_WITH_PTSCOTCH, - 'hilbert' : PDM._PDM_SPLIT_DUAL_WITH_HILBERT, - 'gnum' : PDM._PDM_SPLIT_DUAL_WITH_IMPLICIT} - -maia_to_pdm_connectivity = {"cell_elmt" : PDM._PDM_CONNECTIVITY_TYPE_CELL_ELMT, - "cell_cell" : PDM._PDM_CONNECTIVITY_TYPE_CELL_CELL, - "cell_face" : PDM._PDM_CONNECTIVITY_TYPE_CELL_FACE, - "cell_edge" : PDM._PDM_CONNECTIVITY_TYPE_CELL_EDGE, - "cell_vtx" : PDM._PDM_CONNECTIVITY_TYPE_CELL_VTX, - "face_elmt" : PDM._PDM_CONNECTIVITY_TYPE_FACE_ELMT, - "face_cell" : PDM._PDM_CONNECTIVITY_TYPE_FACE_CELL, - "face_face" : PDM._PDM_CONNECTIVITY_TYPE_FACE_FACE, - "face_edge" : PDM._PDM_CONNECTIVITY_TYPE_FACE_EDGE, - "face_vtx" : PDM._PDM_CONNECTIVITY_TYPE_FACE_VTX, - "edge_elmt" : PDM._PDM_CONNECTIVITY_TYPE_EDGE_ELMT, - "edge_cell" : PDM._PDM_CONNECTIVITY_TYPE_EDGE_CELL, - "edge_face" : PDM._PDM_CONNECTIVITY_TYPE_EDGE_FACE, - "edge_edge" : PDM._PDM_CONNECTIVITY_TYPE_EDGE_EDGE, - "edge_vtx" : PDM._PDM_CONNECTIVITY_TYPE_EDGE_VTX, - "vtx_elmt" : PDM._PDM_CONNECTIVITY_TYPE_VTX_ELMT, - "vtx_cell" : PDM._PDM_CONNECTIVITY_TYPE_VTX_CELL, - "vtx_face" : PDM._PDM_CONNECTIVITY_TYPE_VTX_FACE, - "vtx_edge" : PDM._PDM_CONNECTIVITY_TYPE_VTX_EDGE, - "vtx_vtx" : PDM._PDM_CONNECTIVITY_TYPE_VTX_VTX, - "elmt_cell" : PDM._PDM_CONNECTIVITY_TYPE_ELMT_CELL, - "elmt_face" : PDM._PDM_CONNECTIVITY_TYPE_ELMT_FACE, - "elmt_edge" : PDM._PDM_CONNECTIVITY_TYPE_ELMT_EDGE, - "elmt_vtx " : PDM._PDM_CONNECTIVITY_TYPE_ELMT_VTX} - -pdm_geometry_kinds = [PDM._PDM_GEOMETRY_KIND_CORNER, PDM._PDM_GEOMETRY_KIND_RIDGE, - PDM._PDM_GEOMETRY_KIND_SURFACIC, PDM._PDM_GEOMETRY_KIND_VOLUMIC] - -def prepare_part_weight(bases_to_block, zone_to_weights): - n_zones = sum([len(zones) for zones in bases_to_block.values()]) - n_parts = sum([len(weights) for weights in zone_to_weights.values()]) - n_part_per_zone = np.empty(n_zones, np.int32) - part_weight = np.empty(n_parts, np.float64) - i = 0 - j = 0 - for base, zones in bases_to_block.items(): - for zone in zones: - zone_path = f"{base}/{PT.get_name(zone)}" - weights = zone_to_weights.get(zone_path, []) - n_part_per_zone[i] = len(weights) - part_weight[j:j+n_part_per_zone[i]] = weights - j += n_part_per_zone[i] - i += 1 - return n_part_per_zone, part_weight - -def set_mpart_reordering(multipart, reorder_options, keep_alive): - renum_cell_method = "PDM_PART_RENUM_CELL_" + reorder_options['cell_renum_method'] - renum_face_method = "PDM_PART_RENUM_FACE_" + reorder_options['face_renum_method'] - renum_vtx_method = "PDM_PART_RENUM_VTX_" + reorder_options['vtx_renum_method'] - part_tool_dic = {'parmetis': 1, 'ptscotch': 2, 'hyperplane': 3} - if "CACHEBLOCKING" in reorder_options['cell_renum_method']: - pdm_part_tool = part_tool_dic[reorder_options['graph_part_tool']] - assert pdm_part_tool <= 2 - cacheblocking_props = np.array([reorder_options['n_cell_per_cache'], - 1, - 1, - reorder_options['n_face_per_pack'], - pdm_part_tool], - dtype='int32', order='c') - elif "HPC" in reorder_options['cell_renum_method']: - pdm_part_tool = part_tool_dic[reorder_options['graph_part_tool']] - cacheblocking_props = np.array([reorder_options['n_cell_per_cache'], # n_cell_per_cache_wanted - 0, # is_asynchrone - 1, # is_vectorisation - reorder_options['n_face_per_pack'], # n_vect_face - pdm_part_tool, # split_method - 1, # n_depth - 1, # is_gauss_seidel - 1, # is_gauss_seidel_cell_face - 1], dtype=np.int32) # enforce_rank_in_subdomain - else: - cacheblocking_props = None - multipart.reordering_set(-1, - renum_cell_method.encode('utf-8'), - cacheblocking_props, - renum_face_method.encode('utf-8')) - multipart.reordering_vtx_set(-1, - renum_vtx_method.encode('utf-8')) - keep_alive.append(cacheblocking_props) - -def set_mpart_dmeshes(multi_part, u_zones, comm, keep_alive): - - is_ngon_3d = lambda z: PT.Zone.has_nface_elements(z) or \ - (PT.Zone.has_ngon_elements(z) and PT.get_child_from_name(PT.Zone.NGonNode(z), 'ParentElements') is not None) - - for i_zone, zone in enumerate(u_zones): - if PT.Zone.n_cell(zone) == 0: # Zone has only vertex - dmesh = cgns_to_pdm_dmesh.cgns_dist_zone_to_pdm_dmesh_vtx(zone, comm) - keep_alive.append(dmesh) - multi_part.dmesh_set(i_zone, dmesh) - #Determine NGON or ELMT - elif PT.Zone.has_ngon_elements(zone): - if is_ngon_3d(zone): - dmesh = cgns_to_pdm_dmesh.cgns_dist_zone_to_pdm_dmesh(zone, comm) - keep_alive.append(dmesh) - multi_part.dmesh_set(i_zone, dmesh) - else: - dmesh_nodal = cgns_to_pdm_dmesh.cgns_dist_zone_to_pdm_dmesh_poly2d(zone, comm) - keep_alive.append(dmesh_nodal) - multi_part.dmesh_nodal_set(i_zone, dmesh_nodal) - else: - dmesh_nodal = cgns_to_pdm_dmesh.cgns_dist_zone_to_pdm_dmesh_nodal(zone, comm, needs_bc=False) - keep_alive.append(dmesh_nodal) - multi_part.dmesh_nodal_set(i_zone, dmesh_nodal) - - -def _add_connectivity(multi_part, l_data, i_zone, n_part): - """ - Enrich dictionnary with additional query of user - """ - wanted_connectivities = ["cell_face", "face_cell", "face_vtx", "face_edge", "edge_vtx"] - for key in wanted_connectivities: - connectivity_type = maia_to_pdm_connectivity[key] - for i_part in range(n_part): - np_entity1_entity2_idx, np_entity1_entity2 = multi_part.connectivity_get(i_zone, i_part, connectivity_type) - l_data[i_part]["np_"+key] = np_entity1_entity2 - if(np_entity1_entity2_idx is not None): - l_data[i_part]["np_"+key+'_idx'] = np_entity1_entity2_idx - - -def _add_ln_to_gn(multi_part, l_data, i_zone, n_part): - """ - Enrich dictionnary with additional query of user - """ - wanted_lngn = ["cell", "face", "vtx", "edge"] - for key in wanted_lngn: - entity_type = maia_to_pdm_entity[key] - for i_part in range(n_part): - l_data[i_part]["np_"+key+'_ln_to_gn'] = multi_part.ln_to_gn_get(i_zone, i_part, entity_type) - -def _add_color(multi_part, l_data, i_zone, n_part): - """ - """ - for i_part in range(n_part): - for key in ['cell', 'face', 'edge', 'vtx']: - l_data[i_part]["np_"+key+'_color'] = multi_part.color_get(i_zone, i_part, maia_to_pdm_entity[key]) - l_data[i_part]["np_thread_color"] = multi_part.thread_color_get(i_zone, i_part) - l_data[i_part]["np_hyperplane_color"] = multi_part.hyper_plane_color_get(i_zone, i_part) - -def _add_graph_comm(multi_part, l_data, i_zone, n_part): - """ - """ - wanted_graph = {'face' : PDM._PDM_MESH_ENTITY_FACE, 'vtx' : PDM._PDM_MESH_ENTITY_VTX, 'edge': PDM._PDM_MESH_ENTITY_EDGE} - for i_part in range(n_part): - for kind, pdm_kind in wanted_graph.items(): - for key, val in multi_part.graph_comm_get(i_zone, i_part, pdm_kind).items(): - l_data[i_part][key.replace('entity', kind)] = val - -def collect_mpart_partitions(multi_part, d_zones, n_part_per_zone, comm, post_options): - """ - """ - concat_pdm_data = lambda i_zone, i_part : {"np_vtx_coord" : multi_part.vtx_coord_get (i_zone, i_part), - "np_vtx_ghost_information" : multi_part.ghost_information_get (i_zone, i_part)} - - all_parts = list() - for i_zone, d_zone in enumerate(d_zones): - - n_part = n_part_per_zone[i_zone] - l_dims = list() - for i_part in range(n_part): - l_dims.append({f'n_{key}': multi_part.n_entity_get(i_zone, i_part, entity) \ - for key,entity in maia_to_pdm_entity.items()}) - l_data = [concat_pdm_data(i_zone, i_part) for i_part in range(n_part)] - _add_connectivity(multi_part, l_data, i_zone, n_part) - _add_ln_to_gn (multi_part, l_data, i_zone, n_part) - _add_color (multi_part, l_data, i_zone, n_part) - _add_graph_comm (multi_part, l_data, i_zone, n_part) - - #For element : additional conversion step to retrieve part elements - if PT.Zone.n_cell(d_zone) > 0 and not PT.Zone.has_ngon_elements(d_zone): # pmesh_nodal has not been computed if NGON were present - pmesh_nodal = multi_part.part_mesh_nodal_get(i_zone) - if pmesh_nodal is not None: - for i_part in range(n_part): - zone_dim = pmesh_nodal.dim_get() - for j, kind in enumerate(pdm_geometry_kinds): - if j <= zone_dim: - l_data[i_part][f"{j}dsections"] = pmesh_nodal.get_sections(kind, i_part) - else: # Section of higher dim than mesh dimension can not be getted (assert in pdm) - l_data[i_part][f"{j}dsections"] = [] - - parts = pdm_part_to_cgns_zone(d_zone, l_dims, l_data, comm, post_options) - all_parts.extend(parts) - - return all_parts - -def part_U_zones(bases_to_block_u, dzone_to_weighted_parts, comm, part_options): - - # Careful ! Some object must be deleted at the very end of the function, - # since they are usefull for pdm - keep_alive = list() - - # Bases_to_block_u is the same for each process, but dzone_to_weighted_parts contains - # partial data - n_part_per_zone, part_weight = prepare_part_weight(bases_to_block_u, dzone_to_weighted_parts) - n_zones = n_part_per_zone.size - - keep_alive.append(n_part_per_zone) - - # Init multipart object - requested_tool = part_options['graph_part_tool'] - if min([PT.Zone.n_cell(z) for zones in bases_to_block_u.values() for z in zones]) == 0: - requested_tool = 'hilbert' - pdm_part_tool = maia_to_pdm_split_tool[requested_tool] - pdm_weight_method = 2 - multi_part = PDM.MultiPart(n_zones, n_part_per_zone, 0, pdm_part_tool, pdm_weight_method, part_weight, comm) - - # Setup - u_zones = [zone for zones in bases_to_block_u.values() for zone in zones] - set_mpart_dmeshes(multi_part, u_zones, comm, keep_alive) - set_mpart_reordering(multi_part, part_options['reordering'], keep_alive) - - #Run and return parts - multi_part.compute() - - post_options = {k:part_options[k] for k in ['part_interface_loc', 'dump_pdm_output', 'output_connectivity', - 'save_all_connectivities', 'additional_ln_to_gn', - 'keep_empty_sections']} - u_parts = collect_mpart_partitions(multi_part, u_zones, n_part_per_zone, comm, post_options) - - del(multi_part) # Force multi_part object to be deleted before n_part_per_zone array - for zone in u_zones: - PT.rm_children_from_name(zone, ':CGNS#MultiPart') - - i = 0 - j = 0 - bases_to_part_u = {} - for base, zones in bases_to_block_u.items(): - bases_to_part_u[base] = [] - for zone in zones: - bases_to_part_u[base].extend(u_parts[j:j+n_part_per_zone[i]]) - j += n_part_per_zone[i] - i += 1 - - del(keep_alive) - return bases_to_part_u - - diff --git a/maia/factory/partitioning/split_U/pdm_part_to_cgns_zone.py b/maia/factory/partitioning/split_U/pdm_part_to_cgns_zone.py deleted file mode 100644 index 0615365f..00000000 --- a/maia/factory/partitioning/split_U/pdm_part_to_cgns_zone.py +++ /dev/null @@ -1,292 +0,0 @@ -import numpy as np -import itertools - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import layouts, np_utils - -import Pypdm.Pypdm as PDM - -def _get_part_dim(dims): - if dims['n_cell'] > 0: - return 3 - elif dims['n_face'] > 0: - return 2 - elif dims['n_edge'] > 0: - return 1 - else: - return 0 - -def dump_pdm_output(p_zone, dims, data): - """ - Write PDM output in part_tree (for debug) - """ - ppart_node = PT.new_child(p_zone, ':CGNS#Ppart', 'UserDefinedData_t') - for dim_key, dim_val in dims.items(): - PT.new_DataArray(dim_key, dim_val, parent=ppart_node) - for data_key, data_val in data.items(): - if isinstance(data_val, np.ndarray): - PT.new_DataArray(data_key, np.copy(data_val), parent=ppart_node) - -def zgc_created_pdm_to_cgns(p_zone, d_zone, dims, data, grid_loc='FaceCenter', zgc_name='ZoneGridConnectivity'): - """ - Create by splitting - """ - if grid_loc not in ['FaceCenter', 'Vertex']: - raise NotImplementedError("Unvalid specified entity") - #Element have been created before, so we can check the kind here - if grid_loc == 'FaceCenter' and not PT.Zone.has_ngon_elements(p_zone): - raise NotImplementedError("FaceCenter GC interfaces can not be used for nodal meshes") - if grid_loc == 'FaceCenter': - entity = 'face' if _get_part_dim(dims) == 3 else 'edge' - _grid_loc = f"{entity.capitalize()}Center" - else: - entity = 'vtx' - _grid_loc = 'Vertex' - - entity_part_bound_proc_idx = data['np_{0}_part_bound_proc_idx'.format(entity)] - entity_part_bound_part_idx = data['np_{0}_part_bound_part_idx'.format(entity)] - entity_part_bound_tmp = data['np_{0}_part_bound' .format(entity)] - - entity_part_bound = entity_part_bound_tmp.reshape((4, entity_part_bound_tmp.shape[0]//4), order='F') - entity_part_bound = entity_part_bound.transpose() - - zgc_n = PT.new_node(zgc_name, 'ZoneGridConnectivity_t', parent=p_zone) - - n_internal_join = entity_part_bound_part_idx.shape[0]-1 - for i_join in range(n_internal_join): - - beg_pl = entity_part_bound_part_idx[i_join ] - end_pl = entity_part_bound_part_idx[i_join+1] - - if( beg_pl != end_pl): - - pl_size = end_pl - beg_pl - pl = np.empty((1, pl_size), order='F', dtype=np.int32) - pl[0] = np.copy(entity_part_bound[beg_pl:end_pl, 0]) - - pld = np.empty((1, pl_size), order='F', dtype=np.int32) - pld[0] = np.copy(entity_part_bound[beg_pl:end_pl, 3]) - - opp_rank = entity_part_bound[beg_pl, 1] - opp_part = entity_part_bound[beg_pl, 2]-1 - - cur_rank, cur_part = MT.conv.get_part_suffix(PT.get_name(p_zone)) - gcname = MT.conv.name_intra_gc(cur_rank, cur_part, opp_rank, opp_part) - join_n = PT.new_GridConnectivity(name = gcname, - donor_name = MT.conv.add_part_suffix(PT.get_name(d_zone), opp_rank, opp_part), - type = 'Abutting1to1', - loc = _grid_loc, - parent = zgc_n) - - PT.new_IndexArray(name='PointList' , value=pl , parent=join_n) - PT.new_IndexArray(name='PointListDonor', value=pld, parent=join_n) - - -def pdm_vtx_to_cgns_grid_coordinates(p_zone, dims, data): - """ - """ - coords = data['np_vtx_coord'] - fields = {'CoordinateX' : coords[0::3], 'CoordinateY' : coords[1::3], 'CoordinateZ' : coords[2::3]} - grid_c = PT.new_GridCoordinates(fields=fields, parent=p_zone) - -def pdm_renumbering_data(p_zone, data): - color_data = PT.new_node('maia#Renumbering', 'UserDefinedData_t') - for entity in ['cell', 'face', 'edge', 'vtx', 'thread', 'hyperplane']: - array = data[f'np_{entity}_color'] - if array is not None: - idx, val = np_utils.compress(array) - PT.new_DataArray(f"{entity.capitalize()}ColorIdx", idx, parent=color_data) - PT.new_DataArray(f"{entity.capitalize()}Color", val, parent=color_data) - if len(PT.get_children(color_data)) > 0: - PT.add_child(p_zone, color_data) - -def save_additional_connectivities(p_zone, data): - connec_data = PT.new_node('maia#Connectivities', 'UserDefinedData_t') - if 'np_cell_face_idx' in data: - PT.new_DataArray('cell_face_idx', data['np_cell_face_idx'], parent=connec_data) - PT.new_DataArray('cell_face', data['np_cell_face'], parent=connec_data) - if 'np_face_edge_idx' in data: - PT.new_DataArray('face_edge_idx', data['np_face_edge_idx'], parent=connec_data) - PT.new_DataArray('face_edge', data['np_face_edge'], parent=connec_data) - if 'np_edge_vtx' in data: - PT.new_DataArray('edge_vtx', data['np_edge_vtx'], parent=connec_data) - if len(PT.get_children(connec_data)) > 0: - PT.add_child(p_zone, connec_data) - -def pdm_elmt_to_cgns_elmt(p_zone, d_zone, dims, data, connectivity_as="Element", keep_empty_sections=False): - """ - """ - if PT.Zone.has_ngon_elements(d_zone) or connectivity_as == 'NGon': - # Use default name if none in tree - nedge_name = 'EdgeElements' - ngon_name = 'NGonElements' - nface_name = 'NFaceElements' - for elt in PT.iter_children_from_label(d_zone, 'Elements_t'): - if PT.Zone.has_ngon_elements(d_zone): - # Input element zone + ouput ngon zone --> don't reuse elt names - if PT.Element.CGNSName(elt) == 'BAR_2': - nedge_name = PT.get_name(elt) - if PT.Element.CGNSName(elt) == 'NGON_n': - ngon_name = PT.get_name(elt) - elif PT.Element.CGNSName(elt) == 'NFACE_n': - nface_name = PT.get_name(elt) - - n_face = dims['n_face'] - n_cell = dims['n_cell'] - if _get_part_dim(dims) == 3: - ngon_er = np.array([1, n_face], np.int32) - if PT.Zone.has_ngon_elements(d_zone): - ngon_eso = data['np_face_vtx_idx'] - ngon_ec = data['np_face_vtx'] - else: #When coming from elements, we have no face_vtx; rebuild it - ngon_eso = data['np_face_edge_idx'] - ngon_ec = PDM.compute_face_vtx_from_face_and_edge(data['np_face_edge_idx'], - data['np_face_edge'], - data['np_edge_vtx']) - - ngon_pe = np.empty((n_face, 2), dtype=np.int32, order='F') - layouts.pdm_face_cell_to_pe_cgns(data['np_face_cell'], ngon_pe) - np_utils.shift_nonzeros(ngon_pe, n_face) - - nface_er = np.array([1, n_cell], np.int32) + n_face - nface_eso = data['np_cell_face_idx'] - nface_ec = data['np_cell_face'] - - ngon_n = PT.new_NGonElements(ngon_name, parent=p_zone, erange=ngon_er, eso=ngon_eso, ec=ngon_ec, pe=ngon_pe) - nface_n = PT.new_NFaceElements(nface_name, parent=p_zone, erange=nface_er, eso=nface_eso, ec=nface_ec) - MT.newGlobalNumbering({'Element' : data['np_face_ln_to_gn']}, ngon_n) - MT.newGlobalNumbering({'Element' : data['np_cell_ln_to_gn']}, nface_n) - - elif _get_part_dim(dims) == 2: - face_edge_idx = data['np_face_edge_idx'] - face_edge = data['np_face_edge'] - edge_vtx = data['np_edge_vtx'] - - n_edge = edge_vtx.size//2 - edge_face_idx, edge_face = PDM.connectivity_transpose(n_edge, face_edge_idx, face_edge) - assert edge_face_idx.size - 1 == n_edge - edge_er = np.array([1, n_edge], np.int32) - nedge_pe = np.empty((n_edge,2), np.int32, order='F') - layouts.strided_connectivity_to_pe(edge_face_idx, edge_face, nedge_pe) - np_utils.shift_nonzeros(nedge_pe, n_edge) - - ngon_er = np.array([1, n_face], np.int32) + n_edge - ngon_eso = face_edge_idx - ngon_ec = PDM.compute_face_vtx_from_face_and_edge(face_edge_idx, face_edge, edge_vtx) - - nedge_n = PT.new_Elements(nedge_name, 'BAR_2', erange=edge_er, econn=edge_vtx, parent=p_zone) - ngon_n = PT.new_NGonElements(ngon_name, parent=p_zone, erange=ngon_er, eso=ngon_eso, ec=ngon_ec) - PT.new_DataArray('ParentElements', nedge_pe, parent=nedge_n) - MT.newGlobalNumbering({'Element' : data['np_edge_ln_to_gn']}, nedge_n) - MT.newGlobalNumbering({'Element' : data['np_face_ln_to_gn']}, ngon_n) - - # Keep element sections + NGON section in case input elt / output ngon, since sections will be needed - # for PL exchange - if not PT.Zone.has_ngon_elements(d_zone): - # if vtx are ordered with: - # 1. unique first - # 2. then non-unique but owned - # 3. then non-unique and non-owned ("ghost") - # Then we keep this information in the tree - if 'np_vtx_ghost_information' in data: - pdm_ghost_info = data['np_vtx_ghost_information'] - is_sorted = lambda a: np.all(a[:-1] <= a[1:]) - if (is_sorted(pdm_ghost_info)): - n_vtx_unique = np.searchsorted(pdm_ghost_info,1) - n_vtx_owned = np.searchsorted(pdm_ghost_info,2) - lnum_node = PT.new_node(':CGNS#LocalNumbering', 'UserDefinedData_t', parent=p_zone) - PT.new_DataArray('VertexSizeUnique', n_vtx_unique, parent=lnum_node) - PT.new_DataArray('VertexSizeOwned', n_vtx_owned, parent=lnum_node) - #Now create sections - elt_section_nodes = PT.Zone.get_ordered_elements(d_zone) - pdm_sections = [data[f'{j}dsections'] for j in range(4)] - assert len(elt_section_nodes) == sum([len(sections) for sections in pdm_sections]) - - # If high dim sections are first in dist tree, reverse section order - if PT.Zone.elt_ordering_by_dim(d_zone) == -1: - pdm_sections = pdm_sections[::-1] - n_elt_cum = 0 - n_elt_cum_d = 0 - jumps_idx = np.cumsum([len(sections) for sections in pdm_sections]) - for i_section, section in enumerate(itertools.chain(*pdm_sections)): - if i_section in jumps_idx: #Reset the dimension shift when changing dim - n_elt_cum_d = 0 - elt = elt_section_nodes[i_section] - n_i_elt = section['np_numabs'].size - if n_i_elt > 0 or keep_empty_sections: - elt_n = PT.new_Elements(PT.get_name(elt), PT.get_value(elt), parent=p_zone, - erange = [n_elt_cum+1, n_elt_cum+n_i_elt], econn = section['np_connec']) - numberings = { - # Original position in the section, - 'Element' : section['np_numabs'] - n_elt_cum_d, - # Original position in the concatenated sections of same dimension - 'Sections' : section['np_numabs'] - } - #Original position in the numbering of all elements of the dim: for example, for faces, this is - # the gnum in the description of all the faces (and not only faces described in sections) - if section['np_parent_entity_g_num'] is not None: - numberings['Entity'] = section['np_parent_entity_g_num'] - # Corresponding face in the array of all faces described by a section, - # after face renumbering - lnum_node = PT.new_node(':CGNS#LocalNumbering', 'UserDefinedData_t', parent=elt_n) - PT.new_DataArray('Entity', section['np_parent_num'], parent=lnum_node) - - MT.newGlobalNumbering(numberings, elt_n) - - if connectivity_as == 'NGon': # Prevent clash with NGON/NFace elements with fake label - elt_n[3] = 'FakeElements_t' - - n_elt_cum += n_i_elt - n_elt_cum_d += PT.Element.Size(elt) - -def pdm_part_to_cgns_zone(dist_zone, l_dims, l_data, comm, options): - """ - """ - #Dims and data should be related to the dist zone and of size n_parts - part_zones = list() - for i_part, (dims, data) in enumerate(zip(l_dims, l_data)): - - n_vtx = dims['n_vtx'] - vtx_lngn = data['np_vtx_ln_to_gn'] - base_dim = _get_part_dim(dims) - if base_dim == 0: # Point cloud - n_cell = 0 - cell_lngn = np.empty(0, dtype=vtx_lngn.dtype) - else: - cell_key = {3: 'n_cell', 2: 'n_face', 1: 'n_edge'}[base_dim] - cell_lngn_key = {3: 'np_cell_ln_to_gn', 2: 'np_face_ln_to_gn', 1: 'np_edge_ln_to_gn'}[base_dim] - n_cell = dims[cell_key] - cell_lngn = data[cell_lngn_key] - - part_zone = PT.new_Zone(name = MT.conv.add_part_suffix(PT.get_name(dist_zone), comm.Get_rank(), i_part), - size = [[n_vtx, n_cell, 0]], - type = 'Unstructured') - - if options['dump_pdm_output']: - dump_pdm_output(part_zone, dims, data) - pdm_vtx_to_cgns_grid_coordinates(part_zone, dims, data) - if base_dim > 0: - pdm_elmt_to_cgns_elmt(part_zone, dist_zone, dims, data, options['output_connectivity'],options['keep_empty_sections']) - - output_loc = options['part_interface_loc'] - zgc_name = 'ZoneGridConnectivity' - zgc_created_pdm_to_cgns(part_zone, dist_zone, dims, data, output_loc, zgc_name) - - pdm_renumbering_data(part_zone, data) - if options['save_all_connectivities']: - save_additional_connectivities(part_zone, data) - - requested_lngn = [key.lower() for key in options['additional_ln_to_gn']] - numberings = {'Vertex' : vtx_lngn} - if base_dim >= 2 and 'edge' in requested_lngn and data['np_edge_ln_to_gn'] is not None: - numberings['Edge'] = data['np_edge_ln_to_gn'] - if base_dim == 3 and 'face' in requested_lngn and data['np_face_ln_to_gn'] is not None: - numberings['Face'] = data['np_face_ln_to_gn'] - numberings['Cell'] = cell_lngn - MT.newGlobalNumbering(numberings, parent=part_zone) - - part_zones.append(part_zone) - - return part_zones diff --git a/maia/factory/partitioning/split_U/test/test_cgns_to_pdm_dmesh.py b/maia/factory/partitioning/split_U/test/test_cgns_to_pdm_dmesh.py deleted file mode 100644 index 05ba1eb9..00000000 --- a/maia/factory/partitioning/split_U/test/test_cgns_to_pdm_dmesh.py +++ /dev/null @@ -1,207 +0,0 @@ -import pytest_parallel -import numpy as np - -import Pypdm.Pypdm as PDM - -import maia -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory.partitioning.split_U import cgns_to_pdm_dmesh as CTP - -from maia import npy_pdm_gnum_dtype -dtype = 'I4' if npy_pdm_gnum_dtype == np.int32 else 'I8' - -@pytest_parallel.mark.parallel(2) -def test_split_point_list_by_dim(comm): - if comm.Get_rank() == 0: - pl1 = np.array([[1,5,3]], np.int32) - pl2 = np.array([[14,13]], np.int32) - elif comm.Get_rank() == 1: - pl1 = np.array([[2,4,6]], np.int32) - pl2 = np.empty((1,0), np.int32) - - pl_list = [pl1,pl2] - range_by_dim = [[0,0], [1,10], [11,20], [21,30]] - splitted_bc = CTP._split_point_list_by_dim(pl_list, range_by_dim, comm) - assert splitted_bc == [[], [pl1], [pl2], []] - -@pytest_parallel.mark.parallel(2) -def test_cgns_dist_zone_to_pdm_dmesh_vtx(comm): - if comm.Get_rank() == 0: - dt = """ -ZoneU Zone_t [[12,0,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0. , 0.5, 1. ]: - CoordinateY DataArray_t R8 [0., 0., 0., 0.5, 0.5, 0.5]: - CoordinateZ DataArray_t R8 [0., 0., 0., 0. , 0. , 0. ]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t [0,6,12]: - Cell DataArray_t [0,0,0]: - """ - elif comm.Get_rank() == 1: - dt = """ -ZoneU Zone_t [[12,0,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0., 0.5, 1.]: - CoordinateY DataArray_t R8 [1., 1., 1., 0., 0., 0.]: - CoordinateZ DataArray_t R8 [0., 0., 0., 1., 1., 1.]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t [6,12,12]: - Cell DataArray_t [0,0,0]: - """ - dist_zone = parse_yaml_cgns.to_node(dt) - dmesh = CTP.cgns_dist_zone_to_pdm_dmesh_vtx(dist_zone, comm) - #No getters for dmesh so we can not check data - assert PT.get_child_from_name(dist_zone, ':CGNS#MultiPart') is not None - -@pytest_parallel.mark.parallel(3) -def test_cgns_dist_zone_to_pdm_dmesh(comm): - if comm.Get_rank() == 0: - dt = """ -ZoneU Zone_t [[18,6,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0. , 0.5, 1. ]: - CoordinateY DataArray_t R8 [0., 0., 0., 0.5, 0.5, 0.5]: - CoordinateZ DataArray_t R8 [0., 0., 0., 0. , 0. , 0. ]: - NGonElements Elements_t [22,0]: - ElementRange IndexRange_t [1,20]: - ElementConnectivity DataArray_t: - I4 : [10, 13, 4, 1, 2, 5, 14, 11, 3, 6, 15, 12, 13, 16, 7, 4, - 5, 8, 17, 14, 6, 9, 18, 15, 1, 2, 11, 10] - ParentElements DataArray_t I4 [[21,0],[21,22],[22,0],[23,0],[23,24],[24,0],[21,0]]: - ElementStartOffset DataArray_t [0,4,8,12,16,20,24,28]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [0,7,20]: - ElementConnectivity DataArray_t [0,28,80]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t [0,6,18]: - Cell DataArray_t [0,2,4]: - """ - elif comm.Get_rank() == 1: - dt = """ -ZoneU Zone_t [[18,6,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0., 0.5, 1.]: - CoordinateY DataArray_t R8 [1., 1., 1., 0., 0., 0.]: - CoordinateZ DataArray_t R8 [0., 0., 0., 1., 1., 1.]: - NGonElements Elements_t [22,0]: - ElementRange IndexRange_t [1,20]: - ElementConnectivity DataArray_t: - I4 : [2, 3, 12, 11, 4, 5, 14, 13, 5, 6, 15, 14, 16, 17, 8, 7, - 17, 18, 9, 8, 4, 5, 2, 1, 5, 6, 3, 2] - ParentElements DataArray_t I4 [[22,0],[23,21],[24,22],[23,0],[24,0],[21,0],[22,0]]: - ElementStartOffset DataArray_t [28,32,36,40,44,48,52,56]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [7,14,20]: - ElementConnectivity DataArray_t [28,56,80]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t [6,12,18]: - Cell DataArray_t [2,3,4]: - """ - elif comm.Get_rank() == 2: - dt = """ -ZoneU Zone_t [[18,6,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0., 0.5, 1.]: - CoordinateY DataArray_t R8 [0.5, 0.5, 0.5, 1., 1., 1.]: - CoordinateZ DataArray_t R8 [1., 1., 1., 1., 1. , 1.]: - NGonElements Elements_t [22,0]: - ElementRange IndexRange_t [1,20]: - ElementConnectivity DataArray_t: - I4 : [7, 8, 5, 4, 8, 9, 6, 5, 10, 11, 14, 13, 11, 12, 15, 14, - 13, 14, 17, 16, 14, 15, 18, 17] - ParentElements DataArray_t I4 [[23,0],[24,0],[21,0],[22,0],[23,0],[24,0]]: - ElementStartOffset DataArray_t [56,60,64,68,72,76,80]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [14,20,20]: - ElementConnectivity DataArray_t [56,80,80]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t [12,18,18]: - Cell DataArray_t [3,4,4]: - """ - - dist_zone = parse_yaml_cgns.to_node(dt) - - dmesh = CTP.cgns_dist_zone_to_pdm_dmesh(dist_zone, comm) - #No getters for dmesh so we can not check data - assert PT.get_child_from_name(dist_zone, ':CGNS#MultiPart') is not None - -@pytest_parallel.mark.parallel(2) -def test_cgns_dist_zone_to_pdm_dmesh_poly2d(comm): - dist_tree = maia.factory.generate_dist_block(5, "QUAD_4", comm) - maia.algo.dist.convert_elements_to_ngon(dist_tree, comm) - dist_zone = PT.get_all_Zone_t(dist_tree)[0] - - dmesh_nodal = CTP.cgns_dist_zone_to_pdm_dmesh_poly2d(dist_zone, comm) - dims = PDM.dmesh_nodal_get_g_dims(dmesh_nodal) - assert dims['n_cell_abs'] == 0 - assert dims['n_face_abs'] == 16 - assert dims['n_vtx_abs'] == 25 - assert PT.get_child_from_name(dist_zone, ':CGNS#MultiPart') is not None - - -@pytest_parallel.mark.parallel(3) -def test_cgns_dist_zone_to_pdm_dmesh_nodal(comm): - if comm.Get_rank() == 0: - dt = f""" -ZoneU Zone_t [[18,6,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0. , 0.5, 1. ]: - CoordinateY DataArray_t R8 [0., 0., 0., 0.5, 0.5, 0.5]: - CoordinateZ DataArray_t R8 [0., 0., 0., 0. , 0. , 0. ]: - Hexa Elements_t [17,0]: - ElementRange IndexRange_t [1,4]: - ElementConnectivity DataArray_t {dtype} [1,2,5,4,10,11,14,13, 2,3,6,5,11,12,15,14]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [0,2,4]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t {dtype} [0,6,18]: - Cell DataArray_t {dtype} [0,2,4]: - """ - expected_dnface = 7 - expected_facecell = [1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 4, 0] - elif comm.Get_rank() == 1: - dt = f""" -ZoneU Zone_t [[18,6,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0., 0.5, 1.]: - CoordinateY DataArray_t R8 [1., 1., 1., 0., 0., 0.]: - CoordinateZ DataArray_t R8 [0., 0., 0., 1., 1., 1.]: - Hexa Elements_t [17,0]: - ElementRange IndexRange_t [1,4]: - ElementConnectivity DataArray_t {dtype} [4,5,8,7,13,14,17,16]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [2,3,4]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t {dtype} [6,12,18]: - Cell DataArray_t {dtype} [2,3,4]: - """ - expected_dnface = 5 - expected_facecell = [2, 1, 1, 3, 2, 0, 2, 4, 3, 0] - elif comm.Get_rank() == 2: - dt = f""" -ZoneU Zone_t [[18,6,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 0.5, 1., 0., 0.5, 1.]: - CoordinateY DataArray_t R8 [0.5, 0.5, 0.5, 1., 1., 1.]: - CoordinateZ DataArray_t R8 [1., 1., 1., 1., 1. , 1.]: - Hexa Elements_t [17,0]: - ElementRange IndexRange_t [1,4]: - ElementConnectivity DataArray_t {dtype} [5,6,9,8,14,15,18,17]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [3,4,4]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t {dtype} [12,18,18]: - Cell DataArray_t {dtype} [3,4,4]: - """ - expected_dnface = 8 - expected_facecell = [3, 4, 1, 0, 3, 0, 4, 0, 2, 0, 4, 0, 3, 0, 4, 0] - - dist_zone = parse_yaml_cgns.to_node(dt) - - dmeshnodal = CTP.cgns_dist_zone_to_pdm_dmesh_nodal(dist_zone, comm) - - assert PT.get_child_from_name(dist_zone, ':CGNS#MultiPart') is not None - dims = PDM.dmesh_nodal_get_g_dims(dmeshnodal) - assert dims['n_cell_abs'] == 4 - assert dims['n_vtx_abs'] == 18 diff --git a/maia/factory/partitioning/split_U/test/test_part_all_zones.py b/maia/factory/partitioning/split_U/test/test_part_all_zones.py deleted file mode 100644 index 948b9062..00000000 --- a/maia/factory/partitioning/split_U/test/test_part_all_zones.py +++ /dev/null @@ -1,84 +0,0 @@ -import pytest_parallel - -import numpy as np -from mpi4py import MPI - -import Pypdm.Pypdm as PDM -import maia.pytree as PT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory.partitioning.split_U import part_all_zones as partU - -def test_prepare_part_weight(): - zones = [PT.new_Zone('ZoneA', type='Unstructured'), - PT.new_Zone('ZoneB', type='Unstructured'), - PT.new_Zone('ZoneC', type='Unstructured')] - - base_to_blocks = {'Base' : zones} - d_zone_to_parts = {'Base/ZoneA' : [.3], 'Base/ZoneB' : [], 'Base/ZoneC' : [.2,.5,.3]} - n_part_per_zone, part_weight = partU.prepare_part_weight(base_to_blocks, d_zone_to_parts) - assert (n_part_per_zone == [1,0,3]).all() - assert (part_weight == [.3,.2,.5,.3]).all() - - base_to_blocks = {'Base' : [zones[0]], 'Base2' : zones[1:]} - d_zone_to_parts = {'Base2/ZoneC' : [.2,.5,.3], 'Base/ZoneA' : [.3], 'Base2/ZoneB' : []} - n_part_per_zone, part_weight = partU.prepare_part_weight(base_to_blocks, d_zone_to_parts) - assert (n_part_per_zone == [1,0,3]).all() - assert (part_weight == [.3,.2,.5,.3]).all() - -def test_set_mpart_reordering(): - keep_alive = [] - reorder_options = {'cell_renum_method' : 'CUTHILL', 'face_renum_method' : 'LEXICOGRAPHIC', 'vtx_renum_method' : 'SORT_INT_EXT'} - n_part_per_zones = np.array([1,2], dtype=np.int32) - mpart = PDM.MultiPart(2, np.array([1,2], dtype=np.int32), 0, 1, 1, None, MPI.COMM_SELF) - partU.set_mpart_reordering(mpart, reorder_options, keep_alive) - -@pytest_parallel.mark.parallel(2) -def test_set_mpart_dmeshes(comm): - dtype = 'I4' if pdm_dtype == np.int32 else 'I8' - dt = f""" -ZoneA Zone_t [[1,1,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 []: - CoordinateY DataArray_t R8 []: - CoordinateZ DataArray_t R8 []: - NGonElements Elements_t [22,0]: - ElementRange IndexRange_t {dtype} [1, 1]: - ElementConnectivity DataArray_t {dtype} []: - ElementStartOffset DataArray_t {dtype} [0]: - ParentElements DataArray_t {dtype} [[],[]]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [0,0,0]: - ElementConnectivity DataArray_t {dtype} [0,0,0]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t {dtype} [0,0,0]: - Cell DataArray_t {dtype} [0,0,0]: -ZoneB Zone_t [[1,1,0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 []: - CoordinateY DataArray_t R8 []: - CoordinateZ DataArray_t R8 []: - Hexa Elements_t [17,0]: - ElementRange IndexRange_t [1,1]: - ElementConnectivity DataArray_t {dtype} []: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t {dtype} [0,0,0]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t {dtype} [0,0,0]: - Cell DataArray_t {dtype} [0,0,0]: -""" - dzones = parse_yaml_cgns.to_nodes(dt) - - # Empty array is badly shaped - pe = PT.get_node_from_name(dzones[0], 'ParentElements') - pe[1] = pe[1].reshape((-1,2), order='F') - - keep_alive = [] - - mpart = PDM.MultiPart(2, np.array([1,2], dtype=np.int32), 0, 1, 1, None, comm) - partU.set_mpart_dmeshes(mpart, dzones, comm, keep_alive) - assert len(keep_alive) == len(dzones) - diff --git a/maia/factory/partitioning/split_U/test/test_pdm_part_to_cgns_zone.py b/maia/factory/partitioning/split_U/test/test_pdm_part_to_cgns_zone.py deleted file mode 100644 index 992d8002..00000000 --- a/maia/factory/partitioning/split_U/test/test_pdm_part_to_cgns_zone.py +++ /dev/null @@ -1,187 +0,0 @@ -import pytest -import numpy as np -from mpi4py import MPI - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.factory.partitioning.split_U import pdm_part_to_cgns_zone as PTC - -def test_dump_pdm_output(): - p_zone = PT.new_Zone('Zone.P0.N0', type='Unstructured') - dims = {'n_vtx' : 3} - data = {'np_vtx_coord' : np.array([1,2,3, 4,5,6, 7,8,9], dtype=np.float64), - 'not_numpy' : "should_not_be_dumped" } - PTC.dump_pdm_output(p_zone, dims, data) - dump_node = PT.get_node_from_path(p_zone, ':CGNS#Ppart') - assert dump_node is not None - assert PT.get_child_from_name(dump_node, 'n_vtx')[1] == 3 - assert (PT.get_child_from_name(dump_node, 'np_vtx_coord')[1] == data['np_vtx_coord']).all() - assert PT.get_child_from_name(dump_node, 'not_numpy') is None - -def test_pdm_vtx_to_cgns_grid_coordinates(): - p_zone = PT.new_Zone('Zone.P0.N0', type='Unstructured') - dims = {'n_vtx' : 3} - data = {'np_vtx_coord' : np.array([1,2,3, 4,5,6, 7,8,9], dtype=np.float64)} - - PTC.pdm_vtx_to_cgns_grid_coordinates(p_zone, dims, data) - grid_co = PT.get_node_from_path(p_zone, 'GridCoordinates') - for co in ['CoordinateX', 'CoordinateY', 'CoordinateZ']: - assert PT.get_child_from_name(grid_co, co)[1].dtype == np.float64 - assert (PT.get_child_from_name(grid_co, 'CoordinateX')[1] == [1,4,7]).all() - assert (PT.get_child_from_name(grid_co, 'CoordinateY')[1] == [2,5,8]).all() - assert (PT.get_child_from_name(grid_co, 'CoordinateZ')[1] == [3,6,9]).all() - -@pytest.mark.parametrize("grid_loc",['FaceCenter', 'Vertex']) -def test_zgc_created_pdm_to_cgns(grid_loc): - d_zone = PT.new_Zone('ZoneA', type='Unstructured') - p_zone = PT.new_Zone('ZoneA.P0.N0', type='Unstructured') - PT.new_NGonElements(parent=p_zone) # Create NGon to escape NotImplementedError - dims = {'n_cell': 1} # Just to trigger 3d - data = {'np_face_part_bound_proc_idx' : np.array([0,0,7]), - 'np_face_part_bound_part_idx' : np.array([0,0,7]), - 'np_face_part_bound' : np.array([1,1,1,97,2,1,1,101,3,1,1,105,4,1,1,109,5,1,1,114,6,1,1,118,7,1,1,119]), - 'np_vtx_part_bound_proc_idx' : np.array([0,0,16]), - 'np_vtx_part_bound_part_idx' : np.array([0,0,16]), - 'np_vtx_part_bound' : np.array([63,1,1,71,71,1,1,63,64,1,1,72,72,1,1,64,65,1,1,73,73,1,1,65, - 66,1,1,74,74,1,1,66,67,1,1,75,75,1,1,67,68,1,1,76,76,1,1,68, - 69,1,1,77,77,1,1,69,70,1,1,78,78,1,1,70]) - } - PTC.zgc_created_pdm_to_cgns(p_zone, d_zone, dims, data, grid_loc) - gc_n = PT.get_node_from_name(p_zone, 'JN.P0.N0.LT.P1.N0') - assert PT.get_value(gc_n) == 'ZoneA.P1.N0' - assert PT.GridConnectivity.is1to1(gc_n) - assert PT.Subset.GridLocation(gc_n) == grid_loc - if grid_loc == 'FaceCenter': - assert (PT.get_value(PT.get_node_from_name(gc_n, 'PointList')) == data['np_face_part_bound'][::4]).all() - assert (PT.get_value(PT.get_node_from_name(gc_n, 'PointListDonor')) == data['np_face_part_bound'][3::4]).all() - elif grid_loc == 'Vertex': - assert (PT.get_value(PT.get_node_from_name(gc_n, 'PointListDonor')) == data['np_vtx_part_bound'][3::4]).all() - -def test_pdm_elmt_to_cgns_elmt_ngon(): - d_zone = PT.new_Zone('Zone', type='Unstructured') - PT.new_NGonElements('NGonElements', parent=d_zone) #Dist zone must have a Ngon Node to determine NGon/Element output - p_zone = PT.new_Zone('Zone.P0.N0', type='Unstructured') - dims = {'n_section' :0, 'n_face' : 6, 'n_cell':1} - data = {'np_face_cell' : np.array([1,0,1,0,1,0,1,0,1,0,1,0], dtype=np.int32), - 'np_face_vtx' : np.array([5, 7, 3, 1, 2, 4, 8, 6, 1, 2, 6, 5, 7, 8, 4, 3, 3, - 4, 2, 1, 5, 6, 8, 7], dtype=np.int32), - 'np_face_vtx_idx' : np.array([0,4,8,12,16,20,24], dtype=np.int32), - 'np_face_ln_to_gn' : np.array([12,5,9,13,18,4], dtype=pdm_dtype), - 'np_cell_face' : np.array([1,2,3,4,5,6], dtype=np.int32), - 'np_cell_face_idx' : np.array([0,6], dtype=np.int32), - 'np_cell_ln_to_gn' : np.array([42], dtype=pdm_dtype)} - - PTC.pdm_elmt_to_cgns_elmt(p_zone, d_zone, dims, data) - - ngon_n = PT.get_node_from_path(p_zone, 'NGonElements') - assert (PT.get_node_from_path(ngon_n, 'ElementStartOffset')[1] == data['np_face_vtx_idx']).all() - assert (PT.get_node_from_path(ngon_n, 'ElementConnectivity')[1] == data['np_face_vtx']).all() - assert (PT.get_node_from_path(ngon_n, 'ElementRange')[1] == [1,6]).all() - assert (PT.get_value(MT.getGlobalNumbering(ngon_n, 'Element')) == data['np_face_ln_to_gn']).all() - - nface_n = PT.get_node_from_path(p_zone, 'NFaceElements') - assert (PT.get_node_from_path(nface_n, 'ElementStartOffset')[1] == data['np_cell_face_idx']).all() - assert (PT.get_node_from_path(nface_n, 'ElementConnectivity')[1] == data['np_cell_face']).all() - assert (PT.get_node_from_path(nface_n, 'ElementRange')[1] == [7,7]).all() - assert (PT.get_value(MT.getGlobalNumbering(nface_n, 'Element')) == data['np_cell_ln_to_gn']).all() - -def test_pdm_elmt_to_cgns_elmt_elmt(): - d_zone = PT.new_Zone('Zone', type='Unstructured') - PT.new_Elements('NodeB', 'NODE', erange=[5,8], parent=d_zone) - PT.new_Elements('NodeA', 'NODE', erange=[1,4], parent=d_zone) - PT.new_Elements('Bar', 'BAR_2', erange=[9,20], parent=d_zone) - PT.new_Elements('Quad', 'QUAD_4', erange=[21,26], parent=d_zone) - PT.new_Elements('Hexa', 'HEXA_8', erange=[27,27], parent=d_zone) - p_zone = PT.new_Zone('Zone.P0.N0', type='Unstructured') - dims = {'n_section' :2, 'n_elt' : [6,1]} - data = {'0dsections' : [ - {'np_connec' : np.array([3,4], dtype=np.int32), 'np_numabs' : np.array([3,4], dtype=np.int32), - 'np_parent_num' : np.array([3,4]), 'np_parent_entity_g_num' : np.array([3,4])}, - {'np_connec' : np.array([1,2], dtype=np.int32), 'np_numabs' : np.array([1,2], dtype=np.int32), - 'np_parent_num' : np.array([1,2]), 'np_parent_entity_g_num' : np.array([1,2])}, - ], - '1dsections' : [{'np_connec' : np.array([], dtype=np.int32), 'np_numabs' : np.array([], dtype=np.int32), - 'np_parent_num' : np.array([]), 'np_parent_entity_g_num' : np.array([])}], - '2dsections' : [ - {'np_connec' : np.array([1,4,3,2,1,2,6,5,2,3,7,6,3,4,8,7,1,5,8,4,5,6,7,8], dtype=np.int32), - 'np_numabs' : np.array([12,5,9,13,18,4], dtype=np.int32), - 'np_parent_num' : np.array([1,2,3,4,5,6]), - 'np_parent_entity_g_num' : np.array([101,8,6,12,102,103])} - ], - '3dsections' : [ - {'np_connec' : np.array([1,2,3,4,5,6,7,8], dtype=np.int32), - 'np_numabs' : np.array([42], dtype=pdm_dtype), - 'np_parent_num' : np.array([1]), - 'np_parent_entity_g_num' : None} - ] - } - - PTC.pdm_elmt_to_cgns_elmt(p_zone, d_zone, dims, data) - - node_n = PT.get_node_from_path(p_zone, 'NodeA') - assert (PT.get_node_from_path(node_n, 'ElementConnectivity')[1] == data['0dsections'][0]['np_connec']).all() - assert (PT.get_node_from_path(node_n, 'ElementRange')[1] == [1,2]).all() - - assert PT.get_node_from_path(p_zone, 'Bar') is None - - quad_n = PT.get_node_from_path(p_zone, 'Quad') - assert (PT.get_value(quad_n) == [7,0]).all() - assert (PT.get_node_from_path(quad_n, 'ElementConnectivity')[1] == data['2dsections'][0]['np_connec']).all() - assert (PT.get_node_from_path(quad_n, 'ElementRange')[1] == [5,10]).all() - assert (PT.get_value(MT.getGlobalNumbering(quad_n, 'Element')) == data['2dsections'][0]['np_numabs']).all() - assert (PT.get_value(MT.getGlobalNumbering(quad_n, 'Sections')) == data['2dsections'][0]['np_numabs']).all() - assert (PT.get_value(MT.getGlobalNumbering(quad_n, 'Entity')) == data['2dsections'][0]['np_parent_entity_g_num']).all() - assert (PT.get_value(PT.get_node_from_path(quad_n, ':CGNS#LocalNumbering/Entity')) == \ - data['2dsections'][0]['np_parent_num']).all() - - hexa_n = PT.get_node_from_path(p_zone, 'Hexa') - assert (PT.get_value(hexa_n) == [17,0]).all() - assert (PT.get_node_from_path(hexa_n, 'ElementConnectivity')[1] == data['3dsections'][0]['np_connec']).all() - assert (PT.get_node_from_path(hexa_n, 'ElementRange')[1] == [11,11]).all() - assert (PT.get_value(MT.getGlobalNumbering(hexa_n, 'Element')) == data['3dsections'][0]['np_numabs']).all() - assert (PT.get_value(MT.getGlobalNumbering(hexa_n, 'Sections')) == data['3dsections'][0]['np_numabs']).all() - assert (PT.get_value(PT.get_node_from_path(hexa_n, ':CGNS#LocalNumbering/Entity')) == \ - data['3dsections'][0]['np_parent_num']).all() - -def test_pdm_part_to_cgns_zone(): - # Result of subfunction is not tested here - d_zone = PT.new_Zone('Zone', type='Unstructured') - PT.new_Elements('Quad', 'QUAD_4', erange=[2,7], parent=d_zone) - PT.new_Elements('Hexa', 'HEXA_8', erange=[1,1], parent=d_zone) - l_dims = [{'n_section' :2, 'n_cell' : 1, 'n_vtx': 3, 'n_elt' : [6,1]}] - l_data = [{'0dsections' : [], - '1dsections' : [], - '2dsections' : [ - {'np_connec' : np.array([1,4,3,2,1,2,6,5,2,3,7,6,3,4,8,7,1,5,8,4,5,6,7,8], dtype=np.int32), - 'np_numabs' : np.array([12,5,9,13,18,4], dtype=np.int32), - 'np_parent_num' : np.array([1,2,3,4,5,6]), - 'np_parent_entity_g_num' : np.array([101,8,6,12,102,103])} - ], - '3dsections' : [ - {'np_connec' : np.array([1,2,3,4,5,6,7,8], dtype=np.int32), - 'np_numabs' : np.array([42], dtype=pdm_dtype), - 'np_parent_num' : np.array([1]), - 'np_parent_entity_g_num' : None} - ], - 'np_vtx_coord' : np.array([1,2,3, 4,5,6, 7,8,9], dtype=np.float64), - 'np_vtx_part_bound_proc_idx' : np.array([0,]), - 'np_vtx_part_bound_part_idx' : np.array([0,]), - 'np_vtx_part_bound' : np.empty(0, dtype=np.int32), - 'np_vtx_ghost_information' : np.array([0,0,0,1,1,2]), - 'np_vtx_ln_to_gn' : np.array([2,4,8,16,64,32]), - 'np_cell_ln_to_gn' : np.array([42]), - 'np_cell_color' : None, 'np_face_color' : None, - 'np_edge_color' : None, 'np_vtx_color' : None, - 'np_thread_color' : None, 'np_hyperplane_color' : None, - }] - - options = {'part_interface_loc' : 'Vertex', 'dump_pdm_output' : False, 'output_connectivity':'Element', 'keep_empty_sections':False, 'additional_ln_to_gn':[], 'save_all_connectivities' : False} - part_zones = PTC.pdm_part_to_cgns_zone(d_zone, l_dims, l_data, MPI.COMM_SELF, options) - - assert len(part_zones) == len(l_dims) - for ipart, part_zone in enumerate(part_zones): - assert PT.get_name(part_zone) == PT.get_name(d_zone) + '.P0.N{0}'.format(ipart) - assert (PT.get_value(MT.getGlobalNumbering(part_zone, 'Vertex')) == l_data[ipart]['np_vtx_ln_to_gn']).all() - assert (PT.get_value(MT.getGlobalNumbering(part_zone, 'Cell')) == l_data[ipart]['np_cell_ln_to_gn']).all() diff --git a/maia/factory/partitioning/test/test_partitioning.py b/maia/factory/partitioning/test/test_partitioning.py deleted file mode 100644 index bf33f848..00000000 --- a/maia/factory/partitioning/test/test_partitioning.py +++ /dev/null @@ -1,332 +0,0 @@ -import pytest -import pytest_parallel -from mpi4py import MPI -import numpy as np - -import maia -from maia.pytree.yaml import parse_yaml_cgns, parse_cgns_yaml -from maia.factory import generate_dist_block -from maia.factory import partition_dist_tree -from maia.utils import par_utils, s_numbering - -import maia.pytree as PT -import maia.pytree.maia as MT - -@pytest_parallel.mark.parallel(2) -class Test_split_ngon_2d: - - def get_distree(self, comm): - dist_tree = generate_dist_block(4, "QUAD_4", comm) - maia.algo.dist.convert_elements_to_ngon(dist_tree, comm) - return dist_tree - - def check_elts(self, part_tree, comm): - assert (PT.get_all_CGNSBase_t(part_tree)[0][1] == [2,3]).all() - assert len(PT.get_all_Zone_t(part_tree)) == 1 - edge = PT.get_node_from_name(part_tree, 'EdgeElements') - ngon = PT.get_node_from_name(part_tree, 'NGonElements') - - assert PT.get_child_from_name(edge, 'ParentElements') is not None - assert PT.get_child_from_name(ngon, 'ParentElements') is None - def check_bcs(self, part_tree, comm): - bc_ymin = PT.get_node_from_name(part_tree, 'Ymin') - bc_ymax = PT.get_node_from_name(part_tree, 'Ymax') - bc_xmax = PT.get_node_from_name(part_tree, 'Xmax') - if comm.Get_rank() == 0: - assert (PT.get_child_from_name(bc_ymin, 'PointList')[1] == [[1,2,4]]).all() - assert (PT.get_child_from_name(bc_xmax, 'PointList')[1] == [[8]]).all() - assert bc_ymax is None - elif comm.Get_rank() == 1: - assert (PT.get_child_from_name(bc_ymax, 'PointList')[1] == [[10,12,13]]).all() - assert (PT.get_child_from_name(bc_xmax, 'PointList')[1] == [[4,11]]).all() - assert bc_ymin is None - - @pytest.mark.parametrize("no_pe", [False, True]) - def test_input_pe(self, no_pe, comm): - dist_tree = self.get_distree(comm) - if no_pe: - PT.rm_nodes_from_name(dist_tree, 'ParentElements') - - part_tree = partition_dist_tree(dist_tree, comm, graph_part_tool='hilbert') - self.check_elts(part_tree, comm) - self.check_bcs(part_tree, comm) - - @pytest.mark.parametrize("output_jn_loc", ["Vertex", "FaceCenter"]) - def test_output_loc(self, output_jn_loc, comm): - dist_tree = self.get_distree(comm) - part_tree = partition_dist_tree(dist_tree, comm, graph_part_tool='hilbert', part_interface_loc=output_jn_loc) - - self.check_elts(part_tree, comm) - self.check_bcs(part_tree, comm) - expected_loc = {"Vertex" : "Vertex", "FaceCenter" : "EdgeCenter"} - for gc in PT.iter_nodes_from_label(part_tree, 'GridConnectivity_t'): - assert PT.Subset.GridLocation(gc) == expected_loc[output_jn_loc] - - -@pytest_parallel.mark.parallel(2) -class Test_split_elt_2d: - def get_distree(self, comm): - return generate_dist_block(4, "QUAD_4", comm) - - def check_elts(self, part_tree, comm): - assert (PT.get_all_CGNSBase_t(part_tree)[0][1] == [2,3]).all() - assert len(PT.get_all_Zone_t(part_tree)) == 1 - bar = PT.get_node_from_name(part_tree, 'BAR_2.0') - quad = PT.get_node_from_name(part_tree, 'QUAD_4.0') - if comm.Get_rank() == 0: - assert (PT.get_child_from_name(quad, 'ElementRange')[1] == [1,5]).all() - assert (PT.get_child_from_name(bar, 'ElementRange')[1] == [6,11]).all() - if comm.Get_rank() == 1: - assert (PT.get_child_from_name(quad, 'ElementRange')[1] == [1,4]).all() - assert (PT.get_child_from_name(bar, 'ElementRange')[1] == [5,10]).all() - def check_bcs(self, part_tree, comm): - bc_ymin = PT.get_node_from_name(part_tree, 'Ymin') - bc_ymax = PT.get_node_from_name(part_tree, 'Ymax') - bc_xmax = PT.get_node_from_name(part_tree, 'Xmax') - if comm.Get_rank() == 0: - assert (PT.get_child_from_name(bc_ymin, 'PointList')[1] == [[6,7,8]]).all() - assert (PT.get_child_from_name(bc_xmax, 'PointList')[1] == [[11]]).all() - assert bc_ymax is None - elif comm.Get_rank() == 1: - assert (PT.get_child_from_name(bc_ymax, 'PointList')[1] == [[5,6,7]]).all() - assert (PT.get_child_from_name(bc_xmax, 'PointList')[1] == [[9,10]]).all() - assert bc_ymin is None - - @pytest.mark.parametrize("output_jn_loc", ["Vertex", "FaceCenter"]) - def test_output_loc(self, output_jn_loc, comm): - dist_tree = self.get_distree(comm) - - if output_jn_loc == 'FaceCenter': - with pytest.raises(NotImplementedError): - part_tree = partition_dist_tree(dist_tree, comm, part_interface_loc=output_jn_loc) - else: - part_tree = partition_dist_tree(dist_tree, comm, part_interface_loc=output_jn_loc) - for gc in PT.iter_nodes_from_label(part_tree, 'GridConnectivity_t'): - assert PT.Subset.GridLocation(gc) == "Vertex" - - @pytest.mark.parametrize("output_connectivity", ["Element", "NGon"]) - def test_output_elts(self, output_connectivity, comm): - dist_tree = self.get_distree(comm) - part_tree = partition_dist_tree(dist_tree, comm, graph_part_tool='hilbert', output_connectivity=output_connectivity) - - if output_connectivity == 'Element': - self.check_elts(part_tree, comm) - self.check_bcs(part_tree, comm) - if output_connectivity == 'NGon': - assert PT.get_node_from_name(part_tree, 'QUAD_4*') is None - assert PT.get_node_from_name(part_tree, 'NFaceElements') is None - Test_split_ngon_2d.check_elts(self, part_tree, comm) - Test_split_ngon_2d.check_bcs(self, part_tree, comm) - -@pytest_parallel.mark.parallel(2) -class Test_split_ngon_3d: - def get_distree(self, comm): - return generate_dist_block(4, "Poly", comm) - def check_elts(self, part_tree, comm): - assert (PT.get_all_CGNSBase_t(part_tree)[0][1] == [3,3]).all() - assert len(PT.get_all_Zone_t(part_tree)) == 1 - assert len(PT.get_nodes_from_label(part_tree, 'Elements_t')) == 2 - nfac = PT.get_node_from_name(part_tree, 'NFaceElements') - ngon = PT.get_node_from_name(part_tree, 'NGonElements') - - assert PT.get_child_from_name(ngon, 'ParentElements') is not None - # This depends on partitioning tool version so use more portable test - # if comm.Get_rank() == 0: - # assert (PT.get_child_from_name(ngon, 'ElementRange')[1] == [1,64]).all() - # assert (PT.get_child_from_name(nfac, 'ElementRange')[1] == [65,78]).all() - # elif comm.Get_rank() == 1: - # assert (PT.get_child_from_name(ngon, 'ElementRange')[1] == [1,57]).all() - # assert (PT.get_child_from_name(nfac, 'ElementRange')[1] == [58,70]).all() - assert PT.get_child_from_name(ngon, 'ElementRange')[1][0] == 1 - assert PT.get_child_from_name(nfac, 'ElementRange')[1][0] == PT.get_child_from_name(ngon, 'ElementRange')[1][1] + 1 - assert comm.allreduce(PT.Element.Size(nfac), MPI.SUM) == 27 - assert comm.allreduce(PT.Element.Size(ngon), MPI.SUM) == 121 - - @pytest.mark.parametrize("connectivity", ["NGon+PE", "NFace+NGon", "NFace+NGon+PE"]) - def test_input_connec(self, connectivity, comm): - dist_tree = self.get_distree(comm) - if "NFace" in connectivity: - removePE = not ("PE" in connectivity) - maia.algo.pe_to_nface(dist_tree, comm, removePE) - - part_tree = partition_dist_tree(dist_tree, comm) - - # Disttree should not be modified: - assert (PT.get_node_from_name(dist_tree, 'ParentElements') is not None) == ("PE" in connectivity) - self.check_elts(part_tree, comm) - - @pytest.mark.parametrize("output_jn_loc", ["Vertex", "FaceCenter"]) - def test_output_loc(self, output_jn_loc, comm): - dist_tree = self.get_distree(comm) - part_tree = partition_dist_tree(dist_tree, comm, part_interface_loc=output_jn_loc) - - self.check_elts(part_tree, comm) - for gc in PT.iter_nodes_from_label(part_tree, 'GridConnectivity_t'): - assert PT.Subset.GridLocation(gc) == output_jn_loc - -@pytest_parallel.mark.parallel(2) -class Test_split_elt_3d: - def get_distree(self, comm): - return generate_dist_block(4, "HEXA_8", comm) - def check_elts(self, part_tree, comm): - assert (PT.get_all_CGNSBase_t(part_tree)[0][1] == [3,3]).all() - assert len(PT.get_all_Zone_t(part_tree)) == 1 - - quad = PT.get_node_from_name(part_tree, 'QUAD_4.0') - hexa = PT.get_node_from_name(part_tree, 'HEXA_8.0') - # This depends on partitioning tool version so use more portable test - # if comm.Get_rank() == 0: - # assert (PT.get_child_from_name(hexa, 'ElementRange')[1] == [1,14]).all() - # assert (PT.get_child_from_name(quad, 'ElementRange')[1] == [15,45]).all() - # if comm.Get_rank() == 1: - # assert (PT.get_child_from_name(hexa, 'ElementRange')[1] == [1,13]).all() - # assert (PT.get_child_from_name(quad, 'ElementRange')[1] == [14,36]).all() - assert PT.get_child_from_name(hexa, 'ElementRange')[1][0] == 1 - assert PT.get_child_from_name(quad, 'ElementRange')[1][0] == PT.get_child_from_name(hexa, 'ElementRange')[1][1] + 1 - assert comm.allreduce(PT.Element.Size(hexa), MPI.SUM) == 27 - assert comm.allreduce(PT.Element.Size(quad), MPI.SUM) == 54 - - @pytest.mark.parametrize("output_connectivity", ["Element", "NGon"]) - def test_output(self, output_connectivity, comm): - dist_tree = self.get_distree(comm) - part_tree = partition_dist_tree(dist_tree, comm, output_connectivity=output_connectivity) - - if output_connectivity == 'Element': - self.check_elts(part_tree, comm) - else: - Test_split_ngon_3d.check_elts(self, part_tree, comm) - -@pytest_parallel.mark.parallel([2,3]) -def test_split_point_cloud(comm): - dist_tree = maia.factory.generate_dist_points(13, 'Unstructured', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - - part_zone = PT.get_all_Zone_t(part_tree)[0] - assert PT.Zone.n_cell(part_zone) == 0 - assert comm.allreduce(PT.Zone.n_vtx(part_zone), MPI.SUM) == 13**3 - -PART_TOOLS = ["hilbert"] -if maia.pdm_has_ptscotch: - PART_TOOLS.append("ptscotch") -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("method", PART_TOOLS) -def test_split_lines(method, comm): - - # We can not generate a line directly, so we do a 1D mesh and create bar - n_vtx = 25 - dist_tree = maia.factory.generate_dist_points([n_vtx,1,1], 'U', comm) - dist_base = PT.get_child_from_label(dist_tree, 'CGNSBase_t') - dist_zone = PT.get_child_from_label(dist_base, 'Zone_t') - - PT.set_value(dist_base, [1,3]) - dist_zone[1][0,1] = n_vtx - 1 - - if comm.Get_rank() == 0: - bar_ec = np.repeat(np.arange(n_vtx, dtype=dist_zone[1].dtype), 2)[1:-1] + 1 - dn_bar = n_vtx-1 - else: - bar_ec = np.empty(0, dtype=dist_zone[1].dtype) - dn_bar = 0 - - cell_distri = PT.get_node_from_name(dist_zone, 'Cell')[1] - cell_distri[1] = dn_bar - cell_distri[2] = n_vtx - 1 - bar_elts = PT.new_Elements('Lines', 'BAR_2', erange=[1,n_vtx-1], econn=bar_ec, parent=dist_zone) - MT.newDistribution({'Element' : cell_distri.copy()}, bar_elts) - - part_tree = maia.factory.partition_dist_tree(dist_tree, comm, graph_part_tool=method) - - part_base = PT.get_all_CGNSBase_t(part_tree)[0] - part_zone = PT.get_all_Zone_t(part_tree)[0] - assert (PT.get_value(part_base) == PT.get_value(dist_base)).all() - assert comm.allreduce(PT.Zone.n_cell(part_zone), MPI.SUM) == PT.Zone.n_cell(dist_zone) - assert comm.allreduce(PT.Zone.n_vtx(part_zone), MPI.SUM) == PT.Zone.n_vtx(dist_zone)+1 # One is duplicated - - -@pytest_parallel.mark.parallel(2) -def test_split_structured(comm): - dist_tree = maia.factory.generate_dist_block(11, 'Structured', comm) - - zone_n = PT.get_node_from_path(dist_tree, 'Base/zone') - zonebc_n = PT.get_child_from_name(zone_n, 'ZoneBC') - - xmin_n = PT.get_child_from_name(zonebc_n,'Xmin') - pr = PT.get_value(PT.get_child_from_name(xmin_n,'PointRange')) - - pr_1 = np.copy(pr) - pr_1[1][1] = int(pr[1][1]-1)/2 + 1 - xmin_1 = PT.new_BC('Xmin1_wo_DS',point_range=pr_1,parent=zonebc_n) - MT.newDistribution({'Index': par_utils.uniform_distribution(PT.Subset.n_elem(xmin_1),comm)}, xmin_1) - - pr_2 = np.copy(pr) - pr_2[1][0] = int(pr[1][1]-1)/2 + 1 - xmin_2 = PT.new_BC('Xmin2_w_DS',point_range=pr_2,parent=zonebc_n) - MT.newDistribution({'Index': par_utils.uniform_distribution(PT.Subset.n_elem(xmin_2),comm)}, xmin_2) - bcds = PT.new_node('BCDataSet','BCDataSet_t',value='Null',parent=xmin_2) - PT.new_GridLocation('IFaceCenter',parent=bcds) - pr_ds = np.copy(pr_2) - pr_ds[1][0] = pr_2[1][0]+1 - pr_ds[1][1] = pr_2[1][1]-2 - pr_ds[2][0] = pr_2[2][0]+1 - pr_ds[2][1] = pr_2[2][1]-2 - PT.new_IndexRange(value=pr_ds,parent=bcds) - MT.newDistribution({'Index': par_utils.uniform_distribution(PT.Subset.n_elem(bcds),comm)}, bcds) - index_ds = MT.getDistribution(bcds, 'Index') - - bcd = PT.new_node('DirichletData','BCData_t',parent=bcds) - i_ar = np.arange(pr_ds[0,0], pr_ds[0,1]+1, dtype=np.int32) - j_ar = np.arange(pr_ds[1,0], pr_ds[1,1]+1, dtype=np.int32).reshape(-1,1) - k_ar = np.arange(pr_ds[2,0], pr_ds[2,1]+1, dtype=np.int32).reshape(-1,1,1) - num_face_all = s_numbering.ijk_to_faceiIndex(i_ar, j_ar, k_ar, PT.Zone.CellSize(zone_n), PT.Zone.VertexSize(zone_n)).flatten() - num_face = num_face_all[PT.get_value(index_ds)[0]:PT.get_value(index_ds)[1]] - PT.new_node('LNtoGN_DataSet','DataArray_t',value=num_face,parent=bcd) - - PT.rm_node_from_path(dist_tree, 'Base/zone/ZoneBC/Xmin') - zone_to_parts = maia.factory.partitioning.compute_regular_weights(dist_tree, comm, n_part=5) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm, zone_to_parts=zone_to_parts) - - zone = PT.get_node_from_label(part_tree, 'Zone_t') #Check only on first zone - dist_cell_size = MT.getGlobalNumbering(zone, 'CellSize') - dist_cell_range = MT.getGlobalNumbering(zone, 'CellRange') - if comm.Get_rank() == 0: - expected_range = np.array([[1,4], [1,5], [1,5]], order='F') - elif comm.Get_rank() == 1: - expected_range = np.array([[5,7], [1,7], [6,10]], order='F') - assert PT.get_label(dist_cell_size) == 'DataArray_t' and (PT.get_value(dist_cell_size) == [10,10,10]).all() - assert PT.get_label(dist_cell_range) == 'IndexRange_t' and (PT.get_value(dist_cell_range) == expected_range).all() - - bcds_n_l = PT.get_nodes_from_name(part_tree, 'BCDataSet') - sum_size_bcds = 0 - for bcds_n in bcds_n_l: - index_tab = PT.get_value(MT.getGlobalNumbering(bcds_n, 'Index')) - size_bcds = PT.Subset.n_elem(bcds_n) - assert size_bcds == index_tab.shape[0] - sum_size_bcds += size_bcds - assert np.all(1 <= index_tab) and np.all(index_tab <= 24) - - assert comm.allreduce(sum_size_bcds, MPI.SUM) == 24 - -@pytest_parallel.mark.parallel(2) -def test_split_structured_2d(comm): - dist_tree = maia.factory.generate_dist_block([11,6,1], 'S', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - part_zone = PT.get_all_Zone_t(part_tree)[0] - - assert (PT.Zone.CellSize(part_zone) == [5,5]).all() - assert MT.getGlobalNumbering(part_zone, 'Cell') is not None - assert MT.getGlobalNumbering(part_zone, 'Face') is None - - -@pytest_parallel.mark.parallel(2) -def test_split_structured_1d(comm): - dist_tree = maia.factory.generate_dist_block([10,1,1], 'S', comm) - part_tree = maia.factory.partition_dist_tree(dist_tree, comm) - part_zone = PT.get_all_Zone_t(part_tree)[0] - if comm.Get_rank() == 0: - assert PT.Zone.CellSize(part_zone) == [5] - assert (MT.getGlobalNumbering(part_zone, 'Cell')[1] == [1,2,3,4,5]).all() - elif comm.Get_rank() == 1: - assert PT.Zone.CellSize(part_zone) == [4] - assert (MT.getGlobalNumbering(part_zone, 'Cell')[1] == [6,7,8,9]).all() - assert MT.getGlobalNumbering(part_zone, 'Face') is None - assert len(PT.get_nodes_from_label(part_zone, 'GridConnectivity1to1_t')) \ No newline at end of file diff --git a/maia/factory/partitioning/test/test_post_split.py b/maia/factory/partitioning/test/test_post_split.py deleted file mode 100644 index 9e381575..00000000 --- a/maia/factory/partitioning/test/test_post_split.py +++ /dev/null @@ -1,259 +0,0 @@ -import pytest -import pytest_parallel - -import numpy as np -import maia.pytree as PT - -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory.partitioning import post_split as PS - -def test_pl_idx_ijk(): - yt = """ -Zone Zone_t [[10,9,0], [10,9,0], [10,9,0]]: - ZoneType ZoneType_t "Structured": - ZoneBC ZoneBC_t: - BCa BC_t: - PointList IndexArray_t [[1,1,1,1], [1,2,3,5], [1,1,1,1]]: - GridLocation GridLocation_t "IFaceCenter": - BCb BC_t: #This one is ignored - PointRange IndexRange_t [[1,9], [10,10], [1,9]]: - GridLocation GridLocation_t "JFaceCenter": - """ - zone = parse_yaml_cgns.to_node(yt) - bca = PT.get_node_from_name(zone, 'BCa') - bcb = PT.get_node_from_name(zone, 'BCb') - - PS.pl_as_idx(zone, 'ZoneBC_t/BC_t') - assert np.array_equal(PT.get_node_from_name(bca, 'PointList')[1], [[1,11,21,41]]) - assert PT.get_node_from_name(bcb, 'PointList') is None - - PS.pl_as_ijk(zone, 'ZoneBC_t/BC_t') - assert np.array_equal(PT.get_node_from_name(bca, 'PointList')[1], [[1,1,1,1], [1,2,3,5], [1,1,1,1]]) - assert PT.get_node_from_name(bcb, 'PointList') is None - - # Wrong location - PT.set_value(PT.get_node_from_name(bca, 'GridLocation'), 'Vertexx') - with pytest.raises(ValueError): - PS.pl_as_idx(zone, 'ZoneBC_t/BC_t') - # Wrong zone type - PT.set_value(PT.get_node_from_name(zone, 'ZoneType'), 'Unstructured') - with pytest.raises(AssertionError): - PS.pl_as_idx(zone, 'ZoneBC_t/BC_t') - -@pytest_parallel.mark.parallel(2) -def test_hybrid_jns_as_ijk(comm): - if comm.Get_rank() == 0: - pt = """ - ZoneU.P0.N0 Zone_t: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - GCU GridConnectivity_t "ZoneS.P1.N0": - GridConnectivityType GridConnectivityProperty_t "Abutting1to1": - PointList IndexArray_t [[101,102,103,104]]: - PointListDonor IndexArray_t [[1,11,21,41]]: - GridLocation GridLocation_t "FaceCenter": - GridConnectivityDonorName Descriptor_t "GCS": - """ - elif comm.Get_rank() == 1: - pt = """ - ZoneS.P1.N0 Zone_t [[10,9,0], [10,9,0], [10,9,0]]: - ZoneType ZoneType_t "Structured": - ZGC ZoneGridConnectivity_t: - GCS GridConnectivity_t "ZoneU.P0.N0": - GridConnectivityType GridConnectivityProperty_t "Abutting1to1": - PointList IndexArray_t [[1,11,21,41]]: - PointListDonor IndexArray_t [[101,102,103,104]]: - GridLocation GridLocation_t "IFaceCenter": - GridConnectivityDonorName Descriptor_t "GCS": - """ - part_tree = parse_yaml_cgns.to_cgns_tree(pt) - PS.hybrid_jns_as_ijk(part_tree, comm) - - expected_pl = np.array([[101,102,103,104]]) - expected_pld = np.array([[1,1,1,1], [1,2,3,5], [1,1,1,1]]) - if comm.Get_rank() == 1: - expected_pl, expected_pld = expected_pld, expected_pl - - assert np.array_equal(PT.get_node_from_name(part_tree, 'PointList')[1], expected_pl) - assert np.array_equal(PT.get_node_from_name(part_tree, 'PointListDonor')[1], expected_pld) - -def test_copy_additional_nodes(): - dt = """ -Zone Zone_t: - ZBC ZoneBC_t: - BC BC_t: - PointList IndexArray_t: - GridLocation GridLocation_t 'FaceCenter': - FamilyName FamilyName_t "FAM": - .Solver#BC UserDefinedData_t: - ZGC ZoneGridConnectivity_t: - GC GridConnectivity_t: - PointList IndexArray_t: - GridConnectivityDonorName Descriptor_t "toto": - GridConnectivityProperty GridConnectivityProperty_t: - Periodic Periodic_t: - Translation DataArray_t [1,1,1]: - ZoneIterativeData ZoneIterativeData_t: - FlowSolutionPointers DataArray_y ["FS#1", "FS#2", "FS#3"]: -""" - pt = """ -Zone.P2.N3 Zone_t: - ZBC ZoneBC_t: - BC BC_t: - PointList IndexArray_t: - GridLocation GridLocation_t 'FaceCenter': - ZGC ZoneGridConnectivity_t: - GC GridConnectivity_t: - PointList IndexArray_t: -""" - - dist_zone = parse_yaml_cgns.to_node(dt) - part_zone = parse_yaml_cgns.to_node(pt) - PS.copy_additional_nodes(dist_zone, part_zone) - assert PT.get_label(PT.get_node_from_name(dist_zone, '.Solver#BC')) == PT.get_label(PT.get_node_from_name(part_zone, '.Solver#BC')) - assert PT.get_value(PT.get_node_from_name(dist_zone, 'GridConnectivityDonorName')) == PT.get_value(PT.get_node_from_name(part_zone, 'GridConnectivityDonorName')) - assert (PT.get_value(PT.get_node_from_name(dist_zone, 'Translation')) == \ - PT.get_value(PT.get_node_from_name(part_zone, 'Translation'))).all() - assert PT.get_value(PT.get_node_from_name(dist_zone, 'FlowSolutionPointers')) == \ - PT.get_value(PT.get_node_from_name(part_zone, 'FlowSolutionPointers')) - -def test_update_zone_pointers(): - part_tree = parse_yaml_cgns.to_cgns_tree(""" - ZoneA.P0.N0 Zone_t: - ZoneA.P1.N0 Zone_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - ZonePointers DataArray_t [["ZoneA"], ["ZoneA", "ZoneB"]]: - NumberOfZones DataArray_t [1,2]: - """) - PS.update_zone_pointers(part_tree) - assert (PT.get_value(PT.get_node_from_name(part_tree, 'NumberOfZones')) == [2,2]).all() - assert PT.get_value(PT.get_node_from_name(part_tree, 'ZonePointers'))[0] == ["ZoneA.P0.N0", "ZoneA.P1.N0"] - assert PT.get_value(PT.get_node_from_name(part_tree, 'ZonePointers'))[1] == ["ZoneA.P0.N0", "ZoneA.P1.N0"] - - part_tree = parse_yaml_cgns.to_cgns_tree(""" - ZoneC.P0.N0 Zone_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - ZonePointers DataArray_t [["ZoneA"], ["ZoneA", "ZoneB"]]: - NumberOfZones DataArray_t [1,2]: - """) - PS.update_zone_pointers(part_tree) - assert PT.get_value(PT.get_node_from_name(part_tree, 'ZonePointers')) == [[], []] - - part_tree = parse_yaml_cgns.to_cgns_tree(""" - ZoneA.P0.N0 Zone_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - """) - PS.update_zone_pointers(part_tree) - assert PT.get_node_from_name(part_tree, 'ZonePointers') is None - -def test_generate_related_zsr(): - dt = """ -Zone Zone_t: - ZBC ZoneBC_t: - BC BC_t: - PointList IndexArray_t: - GridLocation GridLocation_t "FaceCenter": - FamilyName FamilyName_t "FAM": - .Solver#BC UserDefinedData_t: - ZGC ZoneGridConnectivity_t: - GC GridConnectivity_t: - PointList IndexArray_t: - GridConnectivityDonorName Descriptor_t "toto": - GridConnectivityProperty GridConnectivityProperty_t: - Periodic Periodic_t: - Translation DataArray_t [1,1,1]: - ZSR_BC ZoneSubRegion_t: - SomeRandomDescriptor Descriptor_t "Let's go party": - BCRegionName Descriptor_t "BC": - ZSR_GC ZoneSubRegion_t: - GridConnectivityRegionName Descriptor_t "GC": -""" - pt = """ -Zone.P2.N3 Zone_t: - ZBC ZoneBC_t: - BC BC_t: - PointList IndexArray_t: - GridLocation GridLocation_t "FaceCenter": - ZGC ZoneGridConnectivity_t: - GC.0 GridConnectivity_t: - PointList IndexArray_t: - GC.1 GridConnectivity_t: - PointList IndexArray_t: - JN.P2.N3.LT.P1.N0 GridConnectivity_t: # Simulate an intra JN -""" - - dist_zone = parse_yaml_cgns.to_node(dt) - part_zone = parse_yaml_cgns.to_node(pt) - PS.generate_related_zsr(dist_zone, part_zone) - assert PT.is_same_node(PT.get_node_from_name(dist_zone, 'ZSR_BC'), PT.get_node_from_name(part_zone, 'ZSR_BC')) - assert PT.get_value(PT.get_node_from_predicates(part_zone, 'ZSR_GC.0/Descriptor_t'))=='GC.0' - assert PT.get_value(PT.get_node_from_predicates(part_zone, 'ZSR_GC.1/Descriptor_t'))=='GC.1' - -def test_split_original_joins(): - pt = """ -ZoneB.P1.N0 Zone_t: - ZGC ZoneGridConnectivity_t: - matchBA GridConnectivity_t "ZoneA": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - PointList IndexArray_t [[8,12,9,20,1]]: - PointListDonor IndexArray_t [[11,21,8,25,13]]: - Donor IndexArray_t [[0,0],[0,0],[1,0],[0,0],[1,0]]: - GridConnectivityDonorName Descriptor_t "matchAB": - :CGNS#GlobalNumbering UserDefinedData_t: - Index DataArray_t [5,3,1,2,6]: -""" - p_tree = parse_yaml_cgns.to_cgns_tree(pt) - p_zone = PT.get_all_Zone_t(p_tree)[0] - PS.split_original_joins(p_tree) - - assert PT.get_node_from_name(p_zone, 'matchBA') is None - assert len(PT.get_nodes_from_name(p_zone, 'matchBA*')) == 2 - new_jn0 = PT.get_node_from_name(p_zone, 'matchBA.0') - new_jn1 = PT.get_node_from_name(p_zone, 'matchBA.1') - assert PT.get_value(new_jn0) == 'ZoneA.P0.N0' - assert PT.get_value(new_jn1) == 'ZoneA.P1.N0' - - assert (PT.get_child_from_name(new_jn0, 'PointList')[1] == [8,12,20]).all() - assert (PT.get_child_from_name(new_jn0, 'PointListDonor')[1] == [11,21,25]).all() - assert (PT.get_node_from_name (new_jn0, 'Index')[1] == [5,3,2]).all() - assert (PT.get_child_from_name(new_jn1, 'PointList')[1] == [9,1]).all() - assert (PT.get_child_from_name(new_jn1, 'PointListDonor')[1] == [8,13]).all() - assert (PT.get_node_from_name (new_jn1, 'Index')[1] == [1,6]).all() - -@pytest_parallel.mark.parallel(2) -def test_update_gc_donor_name(comm): - if comm.Get_rank() == 0: - pt = """ - Base CGNSBase_t: - ZoneA.P0.N0 Zone_t: - ZGC ZoneGridConnectivity_t: - matchAB.0 GridConnectivity_t "ZoneB.P0.N0": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t "matchBA": - matchAB.1 GridConnectivity_t "ZoneB.P1.N0": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t "matchBA": - ZoneB.P0.N0 Zone_t: - ZGC ZoneGridConnectivity_t: - matchBA.0 GridConnectivity_t "ZoneA.P0.N0": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t "matchAB": - """ - expected = ['matchBA.0', 'matchBA.0', 'matchAB.0'] - elif comm.Get_rank() == 1: - pt = """ - Base CGNSBase_t: - ZoneB.P1.N0 Zone_t: - ZGC ZoneGridConnectivity_t: - matchBA.0 GridConnectivity_t "ZoneA.P0.N0": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t "matchAB": - """ - expected = ['matchAB.1'] - p_tree = parse_yaml_cgns.to_cgns_tree(pt) - PS.update_gc_donor_name(p_tree, comm) - - assert [PT.get_value(n) for n in PT.get_nodes_from_name(p_tree, 'GridConnectivityDonorName')] == expected diff --git a/maia/factory/test/test_dcloud_generator.py b/maia/factory/test/test_dcloud_generator.py deleted file mode 100644 index 2bcf031f..00000000 --- a/maia/factory/test/test_dcloud_generator.py +++ /dev/null @@ -1,61 +0,0 @@ -import pytest -import pytest_parallel -import maia.pytree as PT - -from maia.factory import dcloud_generator - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("zone_type", ["Unstructured", "Structured"]) -def test_generate_points(zone_type, comm): - dist_tree = dcloud_generator.generate_dist_points([6,4,1], zone_type, comm) - assert (PT.get_all_CGNSBase_t(dist_tree)[0][1] == [3,3]).all() - - zone = PT.get_all_Zone_t(dist_tree)[0] - assert len(PT.get_children(zone)) == 3 # Only Coords, ZoneType + Distri node - - assert PT.Zone.Type(zone) == zone_type - assert PT.Zone.n_vtx(zone) == 6*4*1 - -@pytest_parallel.mark.parallel(1) -@pytest.mark.parametrize("zone_type", ["Unstructured", "Structured"]) -def test_generate_points_dims(zone_type, comm): - - dist_tree = dcloud_generator.generate_dist_points([6,4], zone_type, comm, origin=[0, 0.], max_coords=[1, 4.]) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - assert (PT.get_all_CGNSBase_t(dist_tree)[0][1] == [2,2]).all() - assert PT.Zone.n_vtx(zone) == 6*4 - assert PT.get_node_from_name(zone, 'CoordinateZ') is None - - # Expand distri - dist_tree = dcloud_generator.generate_dist_points(6, zone_type, comm, origin=[0, 0.], max_coords=[1, 4.]) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - assert (PT.get_all_CGNSBase_t(dist_tree)[0][1] == [2,2]).all() - assert PT.Zone.n_vtx(zone) == 6*6 - assert PT.get_node_from_name(zone, 'CoordinateZ') is None - - dist_tree = dcloud_generator.generate_dist_points(6, zone_type, comm, origin=[0.], max_coords=[2.]) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - assert (PT.get_all_CGNSBase_t(dist_tree)[0][1] == [1,1]).all() - assert PT.Zone.n_vtx(zone) == 6 - assert PT.get_node_from_name(zone, 'CoordinateY') is None - assert PT.get_node_from_name(zone, 'CoordinateZ') is None - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("dim", [3,1]) -def test_generate_points_random(dim, comm): - - if dim == 3: - coords_min = [0., 0., 0.] - coords_max = [1., 1., 2.] - elif dim == 1: - coords_min = [0.] - coords_max = [10] - - dist_tree = dcloud_generator.dpoint_cloud_random_generate(42, coords_min, coords_max, comm) - zone = PT.get_node_from_label(dist_tree, 'Zone_t') - assert (PT.get_all_CGNSBase_t(dist_tree)[0][1] == [dim,dim]).all() - assert PT.Zone.n_vtx(zone) == 42 - - if dim == 1: - assert PT.get_node_from_name(zone, 'CoordinateY') is None - assert PT.get_node_from_name(zone, 'CoordinateZ') is None diff --git a/maia/factory/test/test_dcube_generator.py b/maia/factory/test/test_dcube_generator.py deleted file mode 100644 index 520f060d..00000000 --- a/maia/factory/test/test_dcube_generator.py +++ /dev/null @@ -1,86 +0,0 @@ -import pytest -import pytest_parallel -import maia.pytree as PT - -from maia.factory import dcube_generator - -def check_dims(tree, expected_cell_dim, expected_phy_dim): - base = PT.get_child_from_label(tree, 'CGNSBase_t') - zone = PT.get_child_from_label(base, 'Zone_t') - - assert tuple(PT.get_value(base)) == (expected_cell_dim, expected_phy_dim) - assert PT.get_value(zone).shape[0] == expected_cell_dim - - assert (PT.get_node_from_name(zone, 'CoordinateZ') is not None) == (expected_phy_dim >= 3) - assert (PT.get_node_from_name(zone, 'CoordinateY') is not None) == (expected_phy_dim >= 2) - - assert (PT.get_node_from_name(zone, 'Zmax') is not None) == (expected_cell_dim >= 3) - assert (PT.get_node_from_name(zone, 'Ymin') is not None) == (expected_cell_dim >= 2) - - assert len(PT.get_nodes_from_label(zone, 'BC_t')) == 2*expected_cell_dim - assert (PT.get_node_from_name(zone, 'Face') is not None) == (expected_cell_dim == 3) # Distribution - -@pytest_parallel.mark.parallel([1,3]) -def test_dcube_generate(comm): - # Do not test value since this is a PDM function - dist_tree = dcube_generator.dcube_generate(5, 1., [0., 0., 0.], comm) - - zones = PT.get_all_Zone_t(dist_tree) - assert len(zones) == 1 - zone = zones[0] - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Vertex') is not None - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Cell') is not None - assert len(PT.get_nodes_from_label(zone, 'BC_t')) == 6 - assert PT.get_node_from_path(zone, 'NGonElements/ParentElements')[1].shape[0] + 1 == \ - PT.get_node_from_path(zone, 'NGonElements/ElementStartOffset')[1].shape[0] - -@pytest_parallel.mark.parallel([2]) -def test_dcube_S_generate(comm): - - # Basic version - tree = dcube_generator.generate_dist_block([15,13,12], "S", comm, origin=[0., 0.,0]) - check_dims(tree, 3, 3) - tree = dcube_generator.generate_dist_block([15,13], "S", comm, origin=[0., 0.]) - check_dims(tree, 2, 2) - tree = dcube_generator.generate_dist_block([15,13,1], "S", comm, origin=[0., 0.,0]) - check_dims(tree, 2, 3) - tree = dcube_generator.generate_dist_block([15], "S", comm, origin=[0.]) - check_dims(tree, 1, 1) - tree = dcube_generator.generate_dist_block([15,1], "S", comm, origin=[0., 0.]) - check_dims(tree, 1, 2) - tree = dcube_generator.generate_dist_block([15,1, 1], "S", comm, origin=[0., 0.,0]) - check_dims(tree, 1, 3) - - # Shortcut version - tree = dcube_generator.generate_dist_block(10, "S", comm, origin=[0., 0.,0]) # 3, 3 - check_dims(tree, 3, 3) - tree = dcube_generator.generate_dist_block(10, "S", comm, origin=[0., 0.]) # 2, 2 - check_dims(tree, 2, 2) - tree = dcube_generator.generate_dist_block(10, "S", comm, origin=[0.]) # 1, 1 - check_dims(tree, 1, 1) - -@pytest_parallel.mark.parallel([1,3]) -@pytest.mark.parametrize("cgns_elmt_name", ["TRI_3", "QUAD_4", "TETRA_4", "PENTA_6", "HEXA_8"]) -def test_dcube_nodal_generate(comm, cgns_elmt_name): - # Do not test value since this is a PDM function - dist_tree = dcube_generator.dcube_nodal_generate(5, 1., [0., 0., 0.], cgns_elmt_name, comm) - - zones = PT.get_all_Zone_t(dist_tree) - assert len(zones) == 1 - zone = zones[0] - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Vertex') is not None - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Cell') is not None - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Cell')[1][2] > 0 - assert len(PT.get_nodes_from_label(zone, 'BC_t')) == 4 if cgns_elmt_name in ['TRI_3', 'QUAD_4'] else 6 - #For PENTA_6 we have 1 volumic and 2 surfacic - assert len(PT.get_children_from_label(zone, 'Elements_t')) == 3 if cgns_elmt_name == 'PENTA_6' else 2 - -@pytest_parallel.mark.parallel([2]) -def test_dcube_nodal_generate_ridges(comm): - # Do not test value since this is a PDM function - dist_tree = dcube_generator.dcube_nodal_generate(5, 1., [0., 0., 0.], 'PYRA_5', comm, get_ridges=True) - - zone = PT.get_all_Zone_t(dist_tree)[0] - assert len(PT.get_nodes_from_label(zone, 'BC_t')) == 6 - assert PT.get_names(PT.get_children_from_label(zone, 'Elements_t')) == \ - ['PYRA_5.0', 'TRI_3.0', 'QUAD_4.1', 'BAR_2.0', 'NODE.0'] diff --git a/maia/factory/test/test_dist_from_part.py b/maia/factory/test/test_dist_from_part.py deleted file mode 100644 index 646eb5ed..00000000 --- a/maia/factory/test/test_dist_from_part.py +++ /dev/null @@ -1,459 +0,0 @@ -import pytest -import pytest_parallel -import os -from mpi4py import MPI -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia -from maia.utils import test_utils as TU -from maia.pytree.yaml import parse_yaml_cgns -from maia.factory import generate_dist_block -from maia import npy_pdm_gnum_dtype as pdm_dtype - -from maia.factory import dist_from_part as DFP -dtype = 'I4' if pdm_dtype == np.int32 else 'I8' - -@pytest_parallel.mark.parallel(3) -class Test_discover_nodes_from_matching: - pt = [\ - """ -Zone.P0.N0 Zone_t: - ZBC ZoneBC_t: - BCA BC_t "wall": - Family FamilyName_t "myfamily": - """, - """ -Zone.P1.N0 Zone_t: - ZGC ZoneGridConnectivity_t: - match.0 GridConnectivity_t: - match.1 GridConnectivity_t: - """, - """ -Zone.P2.N0 Zone_t: - ZBC ZoneBC_t: - BCB BC_t "farfield": - GridLocation GridLocation_t "FaceCenter": -Zone.P2.N1 Zone_t: - ZBC ZoneBC_t: - BCA BC_t "wall": - Family FamilyName_t "myfamily": - """] - def test_simple(self, comm): - part_zones = parse_yaml_cgns.to_nodes(self.pt[comm.Get_rank()]) - - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, part_zones, 'ZoneBC_t/BC_t', comm) - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == "BC_t") - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCB')) == "BC_t") - - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, part_zones, ['ZoneBC_t', 'BC_t'], comm) - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == "BC_t") - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCB')) == "BC_t") - - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, part_zones, ["ZoneBC_t", 'BC_t'], comm) - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == "BC_t") - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCB')) == "BC_t") - - dist_zone = PT.new_Zone('Zone') - queries = ["ZoneBC_t", lambda n : PT.get_label(n) == "BC_t" and PT.get_name(n) != "BCA"] - DFP.discover_nodes_from_matching(dist_zone, part_zones, queries, comm) - assert (PT.get_node_from_path(dist_zone, 'ZBC/BCA') == None) - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCB')) == "BC_t") - - def test_short(self, comm): - part_tree = parse_yaml_cgns.to_cgns_tree(self.pt[comm.Get_rank()]) - part_nodes = [PT.get_node_from_path(zone, 'ZBC') for zone in PT.get_all_Zone_t(part_tree)\ - if PT.get_node_from_path(zone, 'ZBC') is not None] - - dist_node = PT.new_node('SomeName', 'UserDefinedData_t') - DFP.discover_nodes_from_matching(dist_node, part_nodes, 'BC_t', comm) - assert PT.get_node_from_path(dist_node, 'BCA') is not None - assert PT.get_node_from_path(dist_node, 'BCB') is not None - - dist_node = PT.new_node('SomeName', 'UserDefinedData_t') - queries = [lambda n : PT.get_label(n) == "BC_t" and PT.get_name(n) != "BCA"] - DFP.discover_nodes_from_matching(dist_node, part_nodes, queries, comm) - assert PT.get_node_from_path(dist_node, 'BCA') is None - assert (PT.get_label(PT.get_node_from_path(dist_node, 'BCB')) == "BC_t") - - def test_getvalue(self, comm): - part_tree = parse_yaml_cgns.to_cgns_tree(self.pt[comm.Get_rank()]) - for zbc in PT.get_nodes_from_name(part_tree, 'ZBC'): - PT.set_value(zbc, 'test') - - # get_value as a string - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), 'ZoneBC_t/BC_t', comm) - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC')) == 'test' - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == None - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), 'ZoneBC_t/BC_t', comm, get_value='none') - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC')) == None - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == None - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), 'ZoneBC_t/BC_t', comm, get_value='all') - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC')) == 'test' - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == 'wall' - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), 'ZoneBC_t/BC_t', comm, get_value='ancestors') - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC')) == 'test' - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == None - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), 'ZoneBC_t/BC_t', comm, get_value='leaf') - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC')) == None - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == 'wall' - - # get_value as a list - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), 'ZoneBC_t/BC_t', comm, get_value=[False, False]) - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC')) == None - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == None - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), 'ZoneBC_t/BC_t', comm, get_value=[True, False]) - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC')) == 'test' - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCA')) == None - - # get_value and search with predicate as lambda - dist_zone = PT.new_Zone('Zone') - queries = ["ZoneBC_t", lambda n : PT.get_label(n) == "BC_t" and PT.get_name(n) != "BCA"] - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), queries, comm, get_value='all') - assert PT.get_node_from_path(dist_zone, 'BCA') is None - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC')) == 'test' - assert PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCB')) == 'farfield' - - def test_with_childs(self, comm): - part_tree = parse_yaml_cgns.to_cgns_tree(self.pt[comm.Get_rank()]) - - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), 'ZoneBC_t/BC_t', comm, - child_list=['FamilyName_t', 'GridLocation']) - assert (PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCA/Family')) == "myfamily") - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCA/Family')) == "FamilyName_t") - assert (PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCB/GridLocation')) == "FaceCenter") - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCB/GridLocation')) == "GridLocation_t") - - dist_zone = PT.new_Zone('Zone') - queries = ["ZoneBC_t", lambda n : PT.get_label(n) == "BC_t" and PT.get_name(n) != "BCA"] - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), queries, comm, - child_list=['FamilyName_t', 'GridLocation']) - assert (PT.get_value(PT.get_node_from_path(dist_zone, 'ZBC/BCB/GridLocation')) == "FaceCenter") - assert (PT.get_label(PT.get_node_from_path(dist_zone, 'ZBC/BCB/GridLocation')) == "GridLocation_t") - - def test_with_rule(self, comm): - part_tree = parse_yaml_cgns.to_cgns_tree(self.pt[comm.Get_rank()]) - - # Exclude from node name - dist_zone = PT.new_Zone('Zone') - queries = ["ZoneBC_t", lambda n : PT.get_label(n) == "BC_t" and not 'A' in PT.get_name(n)] - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), queries, comm, - child_list=['FamilyName_t', 'GridLocation']) - assert PT.get_node_from_path(dist_zone, 'ZBC/BCA') is None - assert PT.get_node_from_path(dist_zone, 'ZBC/BCB') is not None - - # Exclude from node content - dist_zone = PT.new_Zone('Zone') - queries = ["ZoneBC_t", lambda n : PT.get_label(n) == "BC_t" and PT.get_child_from_label(n, 'FamilyName_t') is not None] - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), queries, comm, - child_list=['FamilyName_t', 'GridLocation']) - assert PT.get_node_from_path(dist_zone, 'ZBC/BCA') is not None - assert PT.get_node_from_path(dist_zone, 'ZBC/BCB') is None - - def test_multiple(self, comm): - gc_path = 'ZoneGridConnectivity_t/GridConnectivity_t' - part_tree = parse_yaml_cgns.to_cgns_tree(self.pt[comm.Get_rank()]) - - dist_zone = PT.new_Zone('Zone') - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), gc_path, comm) - assert PT.get_node_from_path(dist_zone, 'ZGC/match.0') is not None - assert PT.get_node_from_path(dist_zone, 'ZGC/match.1') is not None - - dist_zone = PT.new_Zone('Zone') - queries = ["ZoneGridConnectivity_t", "GridConnectivity_t"] - DFP.discover_nodes_from_matching(dist_zone, PT.get_all_Zone_t(part_tree), queries, comm,\ - merge_rule=lambda path : MT.conv.get_split_prefix(path)) - assert PT.get_node_from_path(dist_zone, 'ZGC/match.0') is None - assert PT.get_node_from_path(dist_zone, 'ZGC/match.1') is None - assert PT.get_node_from_path(dist_zone, 'ZGC/match') is not None - - def test_zones(self, comm): - part_tree = PT.new_CGNSTree() - if comm.Get_rank() == 0: - part_base = PT.new_CGNSBase('BaseA', parent=part_tree) - PT.new_Zone('Zone.P0.N0', parent=part_base) - elif comm.Get_rank() == 1: - part_base = PT.new_CGNSBase('BaseB', parent=part_tree) - PT.new_Zone('Zone.withdot.P1.N0', parent=part_base) - elif comm.Get_rank() == 2: - part_base = PT.new_CGNSBase('BaseA', parent=part_tree) - PT.new_Zone('Zone.P2.N0', parent=part_base) - PT.new_Zone('Zone.P2.N1', parent=part_base) - - dist_tree = PT.new_CGNSTree() - DFP.discover_nodes_from_matching(dist_tree, [part_tree], 'CGNSBase_t/Zone_t', comm,\ - merge_rule=lambda zpath : MT.conv.get_part_prefix(zpath)) - - assert len(PT.get_all_Zone_t(dist_tree)) == 2 - assert PT.get_node_from_path(dist_tree, 'BaseA/Zone') is not None - assert PT.get_node_from_path(dist_tree, 'BaseB/Zone.withdot') is not None - -@pytest_parallel.mark.parallel(2) -def test_get_parts_per_blocks(comm): - if comm.Get_rank() == 0: - pt = """ - BaseI CGNSBase_t: - ZoneA.P0.N0 Zone_t: - ZoneA.P0.N1 Zone_t: - ZoneB.P0.N0 Zone_t: - """ - elif comm.Get_rank() == 1: - pt = """ - BaseI CGNSBase_t: - ZoneA.P1.N0 Zone_t: - BaseII CGNSBase_t: - ZoneA.P1.N0 Zone_t: - """ - part_tree = parse_yaml_cgns.to_cgns_tree(pt) - part_per_blocks = DFP.get_parts_per_blocks(part_tree, comm) - if comm.Get_rank() == 0: - assert PT.get_names(part_per_blocks['BaseI/ZoneA']) == ['ZoneA.P0.N0', 'ZoneA.P0.N1'] - assert PT.get_names(part_per_blocks['BaseI/ZoneB']) == ['ZoneB.P0.N0'] - assert PT.get_names(part_per_blocks['BaseII/ZoneA']) == [] - elif comm.Get_rank() == 1: - assert PT.get_names(part_per_blocks['BaseI/ZoneA']) == ['ZoneA.P1.N0'] - assert PT.get_names(part_per_blocks['BaseI/ZoneB']) == [] - assert PT.get_names(part_per_blocks['BaseII/ZoneA']) == ['ZoneA.P1.N0'] - -@pytest_parallel.mark.parallel(1) -def test_get_joins_dist_tree(comm): - pt = """ - BaseI CGNSBase_t: - ZoneA.P0.N0 Zone_t: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - matchAB.0 GridConnectivity_t "ZoneB.P0.N0": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t "matchBA.0": - GridLocation GridLocation_t "Vertex": - PointList IndexArray_t [[8,3,5]]: - :CGNS#GlobalNumbering UserDefinedData_t: - Index DataArray_t [3,1,2]: - :CGNS#GlobalNumbering UserDefinedData_t: - Vertex DataArray_t [10,20,30,40,50,60,70,80,90,100]: - """ - expected_dt = f""" - BaseI CGNSBase_t: - ZoneA Zone_t: - ZoneType ZoneType_t "Unstructured": - ZGC ZoneGridConnectivity_t: - matchAB GridConnectivity_t "ZoneB": - GridConnectivityType GridConnectivityType_t "Abutting1to1": - GridConnectivityDonorName Descriptor_t "matchBA": - GridLocation GridLocation_t "Vertex": - PointList IndexArray_t [[30,50,80]]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t {dtype} [0,3,3]: - """ - part_tree = parse_yaml_cgns.to_cgns_tree(pt) - expected_base = parse_yaml_cgns.to_node(expected_dt) - dist_tree_jn = DFP.get_joins_dist_tree(part_tree, comm) - assert PT.is_same_tree(PT.get_all_CGNSBase_t(dist_tree_jn)[0], expected_base) - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("idx_dim", [3, 2]) -def test_recover_dist_block_size(idx_dim, comm): - if comm.Get_rank() == 0: - pt = """ - Zone.P0.N0 Zone_t [[2,1,0], [3,2,0], [4,3,0]]: #Middle - ZoneType ZoneType_t "Structured": - ZoneGridConnectivity ZoneGridConnectivity_t: - JN.P0.N0.LT.P0.N1 GridConnectivity1to1_t "Zone.P0.N1": - PointRange IndexRange_t [[1,1], [1,3], [1,4]]: - JN.P0.N0.LT.P1.N0 GridConnectivity1to1_t "Zone.P1.N0": - PointRange IndexRange_t [[2,2], [1,3], [1,4]]: - Zone.P0.N1 Zone_t [[2,1,0], [3,2,0], [4,3,0]]: #Left - ZoneType ZoneType_t "Structured": - ZoneGridConnectivity ZoneGridConnectivity_t: - JN.P0.N1.LT.P0.N0 GridConnectivity1to1_t "Zone.P0.N0": - PointRange IndexRange_t [[2,2], [1,3], [1,4]]: - """ - elif comm.Get_rank() == 1: - pt = """ - Zone.P1.N0 Zone_t [[2,1,0], [3,2,0], [4,3,0]]: #Right - ZoneType ZoneType_t "Structured": - ZoneGridConnectivity ZoneGridConnectivity_t: - JN.P1.N0.LT.P0.N0 GridConnectivity1to1_t "Zone.P0.N0": - PointRange IndexRange_t [[1,1], [1,3], [1,4]]: - """ - part_zones = parse_yaml_cgns.to_nodes(pt) - expected = np.array([[4,3,0],[3,2,0],[4,3,0]]) - - if idx_dim == 2: - for zone in part_zones: - zone[1] = zone[1][0:2,:] - for pr in PT.get_nodes_from_label(zone, 'IndexRange_t'): - pr[1] = pr[1][0:2,:] - expected = expected[0:2,:] - - dist_size = DFP._recover_dist_block_size(part_zones, comm) - assert np.array_equal(dist_size, expected) - -@pytest_parallel.mark.parallel(2) -def test_recover_base_iterative_data(comm): - if comm.Get_rank() == 0: - part_tree = parse_yaml_cgns.to_cgns_tree(""" - StaticBase CGNSBase_t: - SimpleBase CGNSBase_t: - BaseIterativeData BaseIterativeData_t [3]: - TimeValues DataArray_t [0., 0.5, 1.]: - Base CGNSBase_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - ZonePointers DataArray_t [["Zone.P0.N0", "Zone.P0.N1"], ["Zone.P0.N0", "Zone.P0.N1"]]: - NumberOfZones DataArray_t [2,2]: - DynamicBase CGNSBase_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - ZonePointers DataArray_t [["ZoneA.P0.N0"], ["ZoneA.P0.N0"]]: - NumberOfZones DataArray_t [1,1]: - """) - elif comm.Get_rank() == 1: - part_tree = parse_yaml_cgns.to_cgns_tree(""" - StaticBase CGNSBase_t: - SimpleBase CGNSBase_t: - BaseIterativeData BaseIterativeData_t [3]: - TimeValues DataArray_t [0., 0.5, 1.]: - Base CGNSBase_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - ZonePointers DataArray_t [["Zone.P1.N0"], ["Zone.P1.N0"]]: - NumberOfZones DataArray_t [1,1]: - DynamicBase CGNSBase_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - ZonePointers DataArray_t [[], ["ZoneB.P1.N0"]]: - NumberOfZones DataArray_t [0,1]: - """) - dist_tree = parse_yaml_cgns.to_cgns_tree(""" - StaticBase CGNSBase_t: - Base CGNSBase_t: - SimpleBase CGNSBase_t: - DynamicBase CGNSBase_t: - """) - expected_tree = parse_yaml_cgns.to_cgns_tree(""" - StaticBase CGNSBase_t: - Base CGNSBase_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - ZonePointers DataArray_t [["Zone"], ["Zone"]]: - NumberOfZones DataArray_t [1,1]: - SimpleBase CGNSBase_t: - BaseIterativeData BaseIterativeData_t [3]: - TimeValues DataArray_t [0., 0.5, 1.]: - DynamicBase CGNSBase_t: - BaseIterativeData BaseIterativeData_t [2]: - TimeValues DataArray_t [0., 1.]: - ZonePointers DataArray_t [["ZoneA"], ["ZoneA", "ZoneB"]]: - NumberOfZones DataArray_t [1,2]: - """) - DFP._recover_base_iterative_data(dist_tree, part_tree, comm) - assert PT.is_same_tree(dist_tree, expected_tree) - -@pytest_parallel.mark.parallel(3) -def test_recover_dist_tree_ngon(comm): - # Value test is already performed in subfunction tests - part_tree = PT.new_CGNSTree() - part_base = PT.new_CGNSBase(parent=part_tree) - if comm.Get_rank() < 2: - distri_ud = MT.newGlobalNumbering() - if comm.Get_rank() == 0: - # part_zone = G.cartNGon((0,0,0), (.5,.5,.5), (3,3,3)) - part_zone = PT.get_all_Zone_t(generate_dist_block(3, 'Poly', MPI.COMM_SELF))[0] - PT.rm_children_from_label(part_zone, 'ZoneBC_t') - PT.rm_nodes_from_name(part_zone, ':CGNS#Distribution') - - vtx_gnum = np.array([1,2,3,6,7,8,11,12,13,16,17,18,21,22,23,26,27,28,31,32,33,36,37,38,41,42,43], pdm_dtype) - cell_gnum = np.array([1,2,5,6,9,10,13,14], pdm_dtype) - ngon_gnum = np.array([1,2,3,6,7,8,11,12,13,16,17,18,21,22,25,26,29,30,33,34,37,38,41,42,45,46,49, - 50,53,54,57,58,61,62,65,66], pdm_dtype) - zbc = PT.new_ZoneBC(parent=part_zone) - bc = PT.new_BC(type='BCWall', point_list=[[1,4,2,3]], parent=zbc) - PT.new_GridLocation('FaceCenter', bc) - MT.newGlobalNumbering({'Index' : np.array([1,2,3,4], pdm_dtype)}, parent=bc) - else: - # part_zone = G.cartNGon((1,0,0), (.5,.5,.5), (3,3,3)) - part_zone = PT.get_all_Zone_t(generate_dist_block(3, 'Poly', MPI.COMM_SELF, origin=[1., 0., 0.]))[0] - PT.rm_children_from_label(part_zone, 'ZoneBC_t') - PT.rm_nodes_from_name(part_zone, ':CGNS#Distribution') - vtx_gnum = np.array([3,4,5, 8,9,10,13,14,15,18,19,20,23,24,25,28,29,30,33,34,35,38,39,40,43,44,45], pdm_dtype) - cell_gnum = np.array([3,4,7,8,11,12,15,16], pdm_dtype) - ngon_gnum = np.array([3,4,5,8,9,10,13,14,15,18,19,20,23,24,27,28,31,32,35,36,39,40,43,44, - 47,48,51,52,55,56,59,60,63,64,67,68], pdm_dtype) - - ngon = PT.get_node_from_path(part_zone, 'NGonElements') - MT.newGlobalNumbering({'Element' : ngon_gnum}, parent=ngon) - - PT.new_DataArray('Vertex', vtx_gnum, parent=distri_ud) - PT.new_DataArray('Cell', cell_gnum, parent=distri_ud) - - part_zone[0] = "Zone.P{0}.N0".format(comm.Get_rank()) - PT.add_child(part_base, part_zone) - PT.add_child(part_zone, distri_ud) - maia.algo.pe_to_nface(part_zone) - - dist_tree = DFP.recover_dist_tree(part_tree, comm) - - dist_zone = PT.get_node_from_name(dist_tree, 'Zone') - assert (dist_zone[1] == [[45,16,0]]).all() - assert (PT.get_node_from_path(dist_zone, 'NGonElements/ElementRange')[1] == [1,68]).all() - assert (PT.get_node_from_path(dist_zone, 'NFaceElements/ElementRange')[1] == [69,84]).all() - assert (PT.get_value(PT.get_node_from_path(dist_zone, 'ZoneBC/BC')) == "BCWall") - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("void_part", [True, False]) -def test_recover_dist_tree_elt(void_part, comm): - mesh_file = os.path.join(TU.mesh_dir, 'hex_prism_pyra_tet.yaml') - dist_tree_bck = maia.io.file_to_dist_tree(mesh_file, comm) - - if void_part: - weights = [1.] if comm.rank == 1 else [] - else: - weights = [.5] - zone_to_parts = {'Base/Zone' : weights} - part_tree = maia.factory.partition_dist_tree(dist_tree_bck, comm, zone_to_parts=zone_to_parts) - maia.transfer.dist_tree_to_part_tree_all(dist_tree_bck, part_tree, comm) - - dist_tree = DFP.recover_dist_tree(part_tree, comm) - - dist_zone = PT.get_node_from_name(dist_tree, 'Zone') - assert (dist_zone[1] == [[11,4,0]]).all() - assert (PT.get_node_from_path(dist_zone, 'Tris/ElementRange')[1] == [7,12]).all() - assert (PT.get_node_from_path(dist_zone, 'Tets/ElementRange')[1] == [16,16]).all() - assert len(PT.get_nodes_from_label(dist_zone, 'BC_t')) == 6 - assert len(PT.get_nodes_from_label(dist_zone, 'ZoneGridConnectivity_t')) == 0 - - for elt in PT.get_nodes_from_label(dist_tree_bck, 'Elements_t'): - PT.rm_node_from_path(elt, ':CGNS#Distribution/ElementConnectivity') - - assert PT.is_same_tree(dist_tree_bck, dist_tree, type_tol=True) #Input tree is pdm dtype - -@pytest_parallel.mark.parallel(3) -def test_recover_dist_tree_s(comm): - mesh_file = os.path.join(TU.mesh_dir, 'S_twoblocks.yaml') - dist_tree_bck = maia.io.file_to_dist_tree(mesh_file, comm) - - part_tree = maia.factory.partition_dist_tree(dist_tree_bck, comm) - - dist_tree = DFP.recover_dist_tree(part_tree, comm) - - # Force GridLocation to appear on dtree bck for comparison - for bc in PT.get_nodes_from_label(dist_tree_bck, 'BC_t'): - if PT.get_child_from_name(bc, 'GridLocation') is None: - PT.new_GridLocation('Vertex', bc) - - assert PT.is_same_tree(dist_tree_bck, dist_tree, type_tol=True) #Recover create I4 zones - diff --git a/maia/factory/test/test_dist_to_full.py b/maia/factory/test/test_dist_to_full.py deleted file mode 100644 index 278cbdcf..00000000 --- a/maia/factory/test/test_dist_to_full.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest -import pytest_parallel - -from mpi4py import MPI - -import maia -import maia.pytree as PT - -from maia.pytree.yaml import parse_yaml_cgns - -from maia.factory import dist_to_full - -def test_reshape_S_arrays(): - yt = """ - Zone Zone_t [[3,2,0], [3,2,0], [2,1, 0]]: - ZoneType ZoneType_t "Structured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]: - FlowSolution FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - Sol DataArray_t [1,2,3,4]: - """ - tree = parse_yaml_cgns.to_cgns_tree(yt) - dist_to_full._reshape_S_arrays(tree) - assert (PT.get_node_from_name(tree, 'Sol')[1] == [ [[1],[3]], [[2],[4]] ]).all() - assert (PT.get_node_from_name(tree, 'CoordinateX')[1] == \ - [ [[1,10], [4,13], [7,16]], [[2,11],[5,14],[8,17]], [[3,12],[6,15], [9,18]] ]).all() - -@pytest_parallel.mark.parallel(2) -def test_dist_to_full_tree_U(comm): - dist_tree = maia.factory.generate_dist_block(3, 'HEXA_8', comm) - full_tree = dist_to_full.dist_to_full_tree(dist_tree, comm, target=1) - - if comm.Get_rank() == 0: - assert full_tree is None - else: - assert PT.get_node_from_name(dist_tree, ':CGNS#Distribution') #Distri info has been removed - tree = maia.factory.generate_dist_block(3, 'HEXA_8', MPI.COMM_SELF) - PT.rm_nodes_from_name(tree, ':CGNS#Distribution') #Remove to compare - assert PT.is_same_tree(tree, full_tree) - -@pytest_parallel.mark.parallel(2) -def test_dist_to_full_tree_S(comm): - dist_tree = maia.factory.generate_dist_block(3, 'S', comm) - full_tree = dist_to_full.dist_to_full_tree(dist_tree, comm, target=1) - if comm.Get_rank() == 0: - assert full_tree is None - else: - for array in PT.get_nodes_from_label(full_tree, 'DataArray_t'): - assert array[1].shape == (3,3,3) #Only coords in this mesh - diff --git a/maia/factory/test/test_dplane_generator.py b/maia/factory/test/test_dplane_generator.py deleted file mode 100644 index 78b4a324..00000000 --- a/maia/factory/test/test_dplane_generator.py +++ /dev/null @@ -1,24 +0,0 @@ -import pytest_parallel -import maia.pytree as PT - -from maia.factory import dplane_generator - -@pytest_parallel.mark.parallel([1,3]) -def test_dplane_generate(comm): - # Do not test value since this is a PDM function - xmin, xmax = 0., 1. - ymin, ymax = 0., 1. - nx, ny = 8, 8 - have_random = 0 - init_random = 1 - dist_tree = dplane_generator.dplane_generate(xmin, xmax, ymin, ymax,\ - have_random, init_random, nx, ny, comm) - - zones = PT.get_all_Zone_t(dist_tree) - assert len(zones) == 1 - zone = zones[0] - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Vertex') is not None - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Cell') is not None - assert len(PT.get_nodes_from_label(zone, 'BC_t')) == 4 - assert PT.get_node_from_path(zone, 'NGonElements/ParentElements')[1].shape[0] + 1 == \ - PT.get_node_from_path(zone, 'NGonElements/ElementStartOffset')[1].shape[0] diff --git a/maia/factory/test/test_dsphere_generator.py b/maia/factory/test/test_dsphere_generator.py deleted file mode 100644 index 5b136d76..00000000 --- a/maia/factory/test/test_dsphere_generator.py +++ /dev/null @@ -1,71 +0,0 @@ -import pytest -import pytest_parallel -import maia.pytree as PT - -from maia.factory import dsphere_generator - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("elmt_name", ["TRI_3", "NGON_n"]) -def test_dsphere_surf_generate(elmt_name, comm): - # Do not test value since this is a PDM function - dist_tree = dsphere_generator.generate_dist_sphere(6, elmt_name, comm) - assert (PT.get_all_CGNSBase_t(dist_tree)[0][1] == [2,3]).all() - - assert len(PT.get_all_Zone_t(dist_tree)) == 1 - zone = PT.get_all_Zone_t(dist_tree)[0] - - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Vertex') is not None - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Cell') is not None - - if elmt_name == "TRI_3": - surf_elt = PT.get_node_from_name(dist_tree, 'TRI_3.0') - else: - surf_elt = PT.get_node_from_name(dist_tree, 'NGonElements') - assert PT.Element.Size(surf_elt) == 720 - - assert len(PT.get_nodes_from_label(dist_tree, 'BC_t')) == 0 - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("elmt_name", ["TETRA_4", "NFACE_n"]) -def test_dsphere_vol_generate(elmt_name, comm): - # Do not test value since this is a PDM function - dist_tree = dsphere_generator.generate_dist_sphere(6, elmt_name, comm) - - assert len(PT.get_all_Zone_t(dist_tree)) == 1 - zone = PT.get_all_Zone_t(dist_tree)[0] - - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Vertex') is not None - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Cell') is not None - - if elmt_name == "TETRA_4": - vol_elt = PT.get_node_from_name(dist_tree, 'TETRA_4.0') - surf_elt = PT.get_node_from_name(dist_tree, 'TRI_3.0') - assert PT.Element.Size(surf_elt) == 720 - else: - vol_elt = PT.get_node_from_name(dist_tree, 'NFaceElements') - surf_elt = PT.get_node_from_name(dist_tree, 'NGonElements') - assert PT.Element.Size(surf_elt) == 9000 - assert PT.Element.Size(vol_elt) == 4320 - - bc = PT.get_node_from_name(dist_tree, 'Skin') - assert PT.get_node_from_name(bc, 'Index')[1][2] == 720 - -@pytest_parallel.mark.parallel(3) -@pytest.mark.parametrize("elmt_name", ["TETRA_4", "NFACE_n"]) -def test_dsphere_hollow_generate(elmt_name, comm): - # Do not test value since this is a PDM function - dist_tree = dsphere_generator.dsphere_hollow_nodal_generate(5, .5, 2, [0., 0., 0.], comm, 4, .5) - - assert len(PT.get_all_Zone_t(dist_tree)) == 1 - zone = PT.get_all_Zone_t(dist_tree)[0] - - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Vertex') is not None - assert PT.get_node_from_path(zone, ':CGNS#Distribution/Cell') is not None - - vol_elt = PT.get_node_from_name(dist_tree, 'PENTA_6.0') - surf_elt = PT.get_node_from_name(dist_tree, 'TRI_3.0') - assert PT.Element.Size(surf_elt) == 1440 - assert PT.Element.Size(vol_elt) == 2880 - - assert len(PT.get_nodes_from_label(dist_tree, 'BC_t')) == 2 - diff --git a/maia/factory/test/test_full_to_dist.py b/maia/factory/test/test_full_to_dist.py deleted file mode 100644 index a4703d65..00000000 --- a/maia/factory/test/test_full_to_dist.py +++ /dev/null @@ -1,143 +0,0 @@ -import pytest -import pytest_parallel - -import maia.pytree as PT - -from maia.pytree.yaml import parse_yaml_cgns - -from maia.factory import full_to_dist - -@pytest_parallel.mark.parallel(2) -def test_distribute_pl_node(comm): - yt = """ - bc BC_t: - PointList IndexArray_t [[10,14,12,16]]: - GridLocation GridLocation_t "CellCenter": - BCDataSet BCDataSet_t: - BCData BCData_t: - Data DataArray_t [1,2,11,11]: - """ - bc = parse_yaml_cgns.to_node(yt) - dist_bc = full_to_dist.distribute_pl_node(bc, comm) - assert PT.get_node_from_path(dist_bc, ':CGNS#Distribution/Index') is not None - assert PT.get_node_from_path(dist_bc, 'BCDataSet/:CGNS#Distribution/Index') is None - assert PT.get_node_from_name(dist_bc, 'PointList')[1].shape == (1,2) - assert PT.get_node_from_name(dist_bc, 'Data')[1].shape == (2,) - - yt = """ - bc BC_t: - PointList IndexArray_t [[10,14,12,16]]: - GridLocation GridLocation_t "CellCenter": - BCDataSet BCDataSet_t: - GridLocation GridLocation_t "Vertex": - PointList IndexArray_t [[100,200]]: - BCData BCData_t: - Data DataArray_t [1,2]: - """ - bc = parse_yaml_cgns.to_node(yt) - dist_bc = full_to_dist.distribute_pl_node(bc, comm) - assert PT.get_node_from_path(dist_bc, ':CGNS#Distribution/Index') is not None - assert PT.get_node_from_path(dist_bc, 'BCDataSet/:CGNS#Distribution/Index') is not None - assert PT.get_node_from_name(dist_bc, 'PointList')[1].shape == (1,2) - assert PT.get_node_from_name(dist_bc, 'Data')[1].shape == (1,) - assert PT.get_node_from_path(dist_bc, 'BCDataSet/PointList')[1].shape == (1,1) - -@pytest_parallel.mark.parallel(3) -def test_distribute_data_node(comm): - rank = comm.Get_rank() - fs = PT.new_FlowSolution(loc='CellCenter') - data1 = PT.new_DataArray('Data1', [2,4,6,8,10,12,14], parent=fs) - data2 = PT.new_DataArray('Data2', [-1,-2,-3,-4,-5,-6,-7], parent=fs) - - dist_fs = full_to_dist.distribute_data_node(fs, comm) - distri_f = [0,3,5,7] - - assert (PT.get_node_from_name(dist_fs, 'Data1')[1] == data1[1][distri_f[rank] : distri_f[rank+1]]).all() - assert (PT.get_node_from_name(dist_fs, 'Data2')[1] == data2[1][distri_f[rank] : distri_f[rank+1]]).all() - -@pytest_parallel.mark.parallel(2) -def test_distribute_element(comm): - yt = """ - Element Elements_t [5,0]: - ElementRange IndexRange_t [16,20]: - ElementConnectivity DataArray_t [4,1,3, 8,2,1, 9,7,4, 11,4,2, 10,4,1]: - """ - elem = parse_yaml_cgns.to_node(yt) - dist_elem = full_to_dist.distribute_element_node(elem, comm) - - assert (PT.Element.Range(dist_elem) == [16,20]).all() - assert PT.get_node_from_path(dist_elem, ':CGNS#Distribution/Element') is not None - if comm.Get_rank() == 0: - assert (PT.get_node_from_name(dist_elem, 'ElementConnectivity')[1] == [4,1,3, 8,2,1, 9,7,4]).all() - else: - assert (PT.get_node_from_name(dist_elem, 'ElementConnectivity')[1] == [11,4,2, 10,4,1]).all() - - for elt_type in ['22', '20']: - yt = f""" - Element Elements_t [{elt_type},0]: - ElementRange IndexRange_t [1,4]: - ElementStartOffset DataArray_t [0,4,8,11,16]: - ElementConnectivity DataArray_t [4,1,3,8, 8,2,3,1, 9,7,4, 11,4,2,10,1]: - ParentElements DataArray_t [[1,0], [2,3], [2,0], [3,0]]: - """ - elem = parse_yaml_cgns.to_node(yt) - dist_elem = full_to_dist.distribute_element_node(elem, comm) - - assert (PT.Element.Range(dist_elem) == [1,4]).all() - assert PT.get_node_from_path(dist_elem, ':CGNS#Distribution/Element') is not None - assert PT.get_node_from_path(dist_elem, ':CGNS#Distribution/ElementConnectivity') is not None - if comm.Get_rank() == 0: - assert (PT.get_child_from_name(dist_elem, 'ElementConnectivity')[1] == [4,1,3,8, 8,2,3,1]).all() - assert (PT.get_child_from_name(dist_elem, 'ElementStartOffset')[1] == [0,4,8]).all() - assert (PT.get_child_from_name(dist_elem, 'ParentElements')[1] == [[1,0],[2,3]]).all() - else: - assert (PT.get_child_from_name(dist_elem, 'ElementConnectivity')[1] == [9,7,4, 11,4,2,10,1]).all() - assert (PT.get_child_from_name(dist_elem, 'ElementStartOffset')[1] == [8,11,16]).all() - assert (PT.get_child_from_name(dist_elem, 'ParentElements')[1] == [[2,0],[3,0]]).all() - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize("owner", [None, 0, 1]) -def test_full_to_dist_tree(owner, comm): - yt = """ - Zone Zone_t [[18, 4, 0]]: - ZoneType ZoneType_t "Unstructured": - Element Elements_t [22,0]: - ElementRange IndexRange_t [1,4]: - ElementStartOffset DataArray_t [0,4,8,11,16]: - ElementConnectivity DataArray_t [4,1,3,8, 8,2,3,1, 9,7,4, 11,4,2,10,1]: - ParentElements DataArray_t [[1,0], [2,3], [2,0], [3,0]]: - ZoneBC ZoneBC_t: - bc BC_t: - PointList IndexArray_t [[10,14,12,16]]: - GridLocation GridLocation_t "CellCenter": - BCDataSet BCDataSet_t: - GridLocation GridLocation_t "Vertex": - PointList IndexArray_t [[100,200]]: - BCData BCData_t: - Data DataArray_t [1,2]: - SolNoPl FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - Array DataArray_t [2.2, 3.3, 1.1, 0.0]: - SolPl FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - PointList IndexArray_t [[3]]: - Array DataArray_t [1000]: - UnrelatedZSR ZoneSubRegion_t: - PointList IndexArray_t [[2, 1]]: - Array DataArray_t [500, 550]: - RelatedZSR ZoneSubRegion_t: - Array DataArray_t [21, 12, 20, 12]: - BCRegionName Descriptor_t "bc": - """ - tree = parse_yaml_cgns.to_cgns_tree(yt) - dist_tree = full_to_dist.full_to_dist_tree(tree, comm, owner=owner) - - zone = PT.get_all_Zone_t(dist_tree)[0] - if comm.Get_rank() == 0: - assert (PT.get_node_from_name(zone, 'ElementConnectivity')[1] == [4,1,3,8, 8,2,3,1]).all() - assert (PT.get_node_from_path(zone, 'SolPl/Array')[1] == [1000]).all() - if comm.Get_rank() == 1: - assert (PT.get_node_from_name(zone, 'ElementConnectivity')[1] == [9,7,4, 11,4,2,10,1]).all() - assert (PT.get_node_from_path(zone, 'SolPl/Array')[1].size == 0) - assert len(PT.get_nodes_from_name(zone, ':CGNS#Distribution')) == 6 - diff --git a/maia/io/__init__.py b/maia/io/__init__.py deleted file mode 100644 index 01aebf0e..00000000 --- a/maia/io/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .cgns_io_tree import file_to_dist_tree, \ - dist_tree_to_file, \ - read_tree, \ - read_links, \ - write_tree, write_trees - -from .part_tree import save_part_tree as part_tree_to_file -from .part_tree import read_part_tree as file_to_part_tree diff --git a/maia/io/_hdf_io_cass.py b/maia/io/_hdf_io_cass.py deleted file mode 100644 index 3f9980e5..00000000 --- a/maia/io/_hdf_io_cass.py +++ /dev/null @@ -1,163 +0,0 @@ -import Converter -import Converter.Filter -import Converter.PyTree as C - -import maia.pytree as PT - -from .fix_tree import fix_point_ranges, ensure_symmetric_gc1to1, fix_zone_datatype,\ - rm_legacy_nodes, add_missing_pr_in_bcdataset - -def add_sizes_to_zone_tree(zone, zone_path, size_data): - """ - Creates the MyArray#Size node using the size_data dict on the given zone - for the following nodes: - - ElementConnectivity array of Element_t nodes - - PointList (or Unstr PointRange) array of BC_t - - PointList array of GC_t, GC1to1_t, BCDataSet_t and ZoneSubRegion_t nodes - """ - for elmt in PT.iter_children_from_label(zone, 'Elements_t'): - elmt_path = zone_path+"/"+elmt[0] - ec_path = elmt_path+"/ElementConnectivity" - if PT.get_child_from_name(elmt, 'ElementStartOffset') is not None: - PT.new_IndexArray('ElementConnectivity#Size', value=size_data[ec_path][2], parent=elmt) - - for zone_bc in PT.iter_children_from_label(zone, 'ZoneBC_t'): - zone_bc_path = zone_path+"/"+zone_bc[0] - for bc in PT.iter_children_from_label(zone_bc, 'BC_t'): - bc_path = zone_bc_path+"/"+bc[0] - if PT.get_child_from_name(bc, 'PointList') is not None: - pl_path = bc_path+"/PointList" - PT.new_IndexArray('PointList#Size', value=size_data[pl_path][2], parent=bc) - for bcds in PT.iter_children_from_label(bc, 'BCDataSet_t'): - if PT.get_child_from_name(bcds, 'PointList') is not None: - pl_path = bc_path+"/"+bcds[0]+"/PointList" - PT.new_IndexArray('PointList#Size', value=size_data[pl_path][2], parent=bcds) - for bcdata, array in PT.get_children_from_predicates(bcds, 'BCData_t/DataArray_t', ancestors=True): - data_path = '/'.join([bc_path, bcds[0], bcdata[0], array[0]]) - PT.new_IndexArray(f'{array[0]}#Size', value=size_data[data_path][2], parent=bcdata) - - for zone_gc in PT.iter_children_from_label(zone, 'ZoneGridConnectivity_t'): - zone_gc_path = zone_path+"/"+zone_gc[0] - gcs = PT.get_children_from_label(zone_gc, 'GridConnectivity_t') \ - + PT.get_children_from_label(zone_gc, 'GridConnectivity1to1_t') - for gc in gcs: - gc_path = zone_gc_path+"/"+gc[0] - if PT.get_child_from_name(gc, 'PointList') is not None: - pl_path = gc_path+"/PointList" - PT.new_IndexArray('PointList#Size', value=size_data[pl_path][2], parent=gc) - if PT.get_child_from_name(gc, 'PointListDonor') is not None: - pld_path = gc_path+"/PointListDonor" - PT.new_IndexArray('PointListDonor#Size', value=size_data[pl_path][2], parent=gc) - assert size_data[pld_path][2] == size_data[pl_path][2] - - for zone_subregion in PT.iter_children_from_label(zone, 'ZoneSubRegion_t'): - zone_subregion_path = zone_path+"/"+zone_subregion[0] - if PT.get_child_from_name(zone_subregion, 'PointList') is not None: - pl_path = zone_subregion_path+"/PointList" - PT.new_IndexArray('PointList#Size', value=size_data[pl_path][2], parent=zone_subregion) - - for flow_sol in PT.iter_children_from_label(zone, 'FlowSolution_t'): - sol_path = zone_path + "/" + PT.get_name(flow_sol) - if PT.get_child_from_name(flow_sol, 'PointList') is not None: - pl_path = sol_path+"/PointList" - PT.new_IndexArray('PointList#Size', value=size_data[pl_path][2], parent=flow_sol) - for array in PT.get_children_from_label(flow_sol, 'DataArray_t'): - #This one is DataArray to be detected in fix_tree.rm_legacy_nodes - if size_data[sol_path+'/'+array[0]][1] != 'MT': - PT.new_DataArray(f'{array[0]}#Size', value=size_data[sol_path+'/'+array[0]][2], parent=flow_sol) - -def add_sizes_to_tree(size_tree, size_data): - """ - Convience function which loops over zones to add size - data in each one. - """ - for base in PT.iter_children_from_label(size_tree, 'CGNSBase_t'): - base_path = '/'+base[0] - for zone in PT.iter_all_Zone_t(base): - zone_path = base_path+"/"+zone[0] - add_sizes_to_zone_tree(zone, zone_path, size_data) - - -def load_size_tree(filename, comm): - """ - Load on all ranks a "size tree" - a size tree is a partial tree that contains only the data needed to distribute the tree: - nb of nodes, nb of elements, size of bcs and gcs... - Convention: - when we load the dimensions of an array "MyArray" without loading the array, - then the dimensions are kept in a "MyArray#Size" node, - at the same level as the array node would be - """ - skeleton_depth = -1 - skeleton_n_data = 4 - - # In order to avoid filesystem overload only 1 proc reads the squeleton, then we broadcast - if(comm.Get_rank() == 0): - size_data = dict() - assert Converter.checkFileType(filename) == "bin_hdf" - size_tree = Converter.PyTree.convertFile2PyTree(filename, - skeletonData=[skeleton_n_data, skeleton_depth], - dataShape=size_data, - format='bin_hdf') - fix_zone_datatype(size_tree, size_data) - add_sizes_to_tree(size_tree, size_data) - fix_point_ranges(size_tree) - pred_1to1 = 'CGNSBase_t/Zone_t/ZoneGridConnectivity_t/GridConnectivity1to1_t' - if PT.get_node_from_predicates(size_tree, pred_1to1) is not None: - ensure_symmetric_gc1to1(size_tree) - add_missing_pr_in_bcdataset(size_tree) - rm_legacy_nodes(size_tree) - else: - size_tree = None - - size_tree = comm.bcast(size_tree, root=0) - - return size_tree - -def load_partial(filename, dist_tree, hdf_filter, comm): - hdf_filter = {f'/{key}' : data for key, data in hdf_filter.items()} - partial_dict_load = C.convertFile2PartialPyTreeFromPath(filename, hdf_filter, comm) - - for path, data in partial_dict_load.items(): - if path.startswith('/'): - path = path[1:] - node = PT.get_node_from_path(dist_tree, path) - node[1] = data - -def load_grid_connectivity_property(filename, tree): - """ - Load the GridConnectivityProperty_t nodes that may be present in joins. - Because the transformation data is stored as a numpy array, these nodes - are not loaded on the previous step. - """ - # Prepare paths - zgc_t_path = 'CGNSBase_t/Zone_t/ZoneGridConnectivity_t' - is_gc = lambda n : PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - gc_prop_paths = [] - for base,zone,zone_gc in PT.iter_children_from_predicates(tree, zgc_t_path, ancestors=True): - for gc in PT.iter_children_from_predicate(zone_gc, is_gc): - gc_prop = PT.get_child_from_label(gc, 'GridConnectivityProperty_t') - if gc_prop is not None: - gc_prop_path = '/'.join([base[0], zone[0], zone_gc[0], gc[0], gc_prop[0]]) - gc_prop_paths.append(gc_prop_path) - - # Load - gc_prop_nodes = Converter.Filter.readNodesFromPaths(filename, gc_prop_paths) - - # Replace with loaded data - for path, gc_prop in zip(gc_prop_paths, gc_prop_nodes): - gc_node_path = '/'.join(path.split('/')[:-1]) - gc_node = PT.get_node_from_path(tree, gc_node_path) - PT.rm_children_from_label(gc_node, 'GridConnectivityProperty_t') - PT.add_child(gc_node, gc_prop) - - -def write_partial(filename, dist_tree, hdf_filter, comm): - hdf_filter = {f'/{key}' : data for key, data in hdf_filter.items()} - C.convertPyTree2FilePartial(dist_tree, filename, comm, hdf_filter, ParallelHDF=True) - -def read_full(filename): - return C.convertFile2PyTree(filename) - -def write_full(filename, tree, links=[]): - C.convertPyTree2File(tree, filename, links=links) diff --git a/maia/io/_hdf_io_h5py.py b/maia/io/_hdf_io_h5py.py deleted file mode 100644 index d59fe3ef..00000000 --- a/maia/io/_hdf_io_h5py.py +++ /dev/null @@ -1,100 +0,0 @@ -from mpi4py import MPI -from h5py import h5p, h5f, h5fd - -import maia.pytree as PT - -from .hdf._hdf_cgns import open_from_path,\ - load_tree_partial, write_tree_partial,\ - load_data_partial, write_data_partial,\ - load_tree_links, write_link -from .fix_tree import fix_point_ranges, corr_index_range_names,\ - ensure_symmetric_gc1to1, rm_legacy_nodes,\ - add_missing_pr_in_bcdataset, check_datasize - -def load_data(names, labels): - """ Function used to determine if the data is heavy or not """ - if len(names) == 1: #First level (Base, CGLibVersion, ...) -> always load + early return - return True - if labels[-1] == 'IndexArray_t': # PointList -> do not load - return False - if labels[-1] == 'DataArray_t': # Arrays -> it depends - if labels[-2] in ['GridCoordinates_t', 'FlowSolution_t', 'DiscreteData_t', \ - 'ZoneSubRegion_t', 'Elements_t']: - return False - if names[-2] in [':elsA#Hybrid']: # Do not load legacy nodes - return False - if names[-2] in [':CGNS#GlobalNumbering']: - return False - if labels[-2] == 'BCData_t' and labels[-3] == 'BCDataSet_t': # Load FamilyBCDataSet, but not BCDataSet - return False - return True - -def load_size_tree(filename, comm): - - if comm.Get_rank() == 0: - size_tree = load_tree_partial(filename, load_data) - rm_legacy_nodes(size_tree) - corr_index_range_names(size_tree) - check_datasize(size_tree) - fix_point_ranges(size_tree) - pred_1to1 = 'CGNSBase_t/Zone_t/ZoneGridConnectivity_t/GridConnectivity1to1_t' - if PT.get_node_from_predicates(size_tree, pred_1to1) is not None: - ensure_symmetric_gc1to1(size_tree) - add_missing_pr_in_bcdataset(size_tree) - else: - size_tree = None - - size_tree = comm.bcast(size_tree, root=0) - - return size_tree - -def load_partial(filename, dist_tree, hdf_filter): - fid = h5f.open(bytes(filename, 'utf-8'), h5f.ACC_RDONLY) - - for path, filter in hdf_filter.items(): - if isinstance(filter, (list, tuple)): - node = PT.get_node_from_path(dist_tree, path) - gid = open_from_path(fid, path) - node[1] = load_data_partial(gid, filter) - -def write_partial(filename, dist_tree, hdf_filter, comm): - - if comm.Get_rank() == 0: - write_tree_partial(dist_tree, filename, load_data) - comm.barrier() - - fapl = h5p.create(h5p.FILE_ACCESS) - fapl.set_driver(h5fd.MPIO) - fapl.set_fapl_mpio(comm, MPI.Info()) - fid = h5f.open(bytes(filename, 'utf-8'), h5f.ACC_RDWR, fapl) - - for path, filter in hdf_filter.items(): - array = PT.get_node_from_path(dist_tree, path)[1] - gid = open_from_path(fid, path) - write_data_partial(gid, array, filter) - gid.close() - - fid.close() - -def read_full(filename): - return load_tree_partial(filename, lambda X,Y: True) - -def read_links(filename): - return load_tree_links(filename) - -def write_full(filename, dist_tree, links=[]): - _dist_tree = PT.shallow_copy(dist_tree) - for link in links: # Links override data, so delete data - PT.rm_node_from_path(_dist_tree, link[3]) - write_tree_partial(_dist_tree, filename, lambda X,Y: True) - - # Add links if any - fid = h5f.open(bytes(filename, 'utf-8'), h5f.ACC_RDWR) - for link in links: - target_dir, target_file, target_node, local_node = link - parent_node_path = PT.path_head(local_node) - local_node_name = PT.path_tail(local_node) - gid = open_from_path(fid, parent_node_path) - write_link(gid, local_node_name, target_file, target_node) - fid.close() - diff --git a/maia/io/cgns_io_tree.py b/maia/io/cgns_io_tree.py deleted file mode 100644 index 78c5a83d..00000000 --- a/maia/io/cgns_io_tree.py +++ /dev/null @@ -1,231 +0,0 @@ -import os -import time -import mpi4py.MPI as MPI - -import maia.pytree as PT -import maia.pytree.maia as MT -import maia.utils.logging as mlog - -from .distribution_tree import add_distribution_info, clean_distribution_info -from .hdf.tree import create_tree_hdf_filter -from .fix_tree import ensure_PE_global_indexing, ensure_signed_nface_connectivity, _enforce_pdm_dtype - -from maia.factory import full_to_dist -from maia.pytree.yaml import parse_yaml_cgns - -def load_size_tree(filename, comm, legacy=False): - if legacy: - from ._hdf_io_cass import load_size_tree - else: - from ._hdf_io_h5py import load_size_tree - return load_size_tree(filename, comm) - -def load_partial(filename, dist_tree, hdf_filter, comm, legacy): - if legacy: - from ._hdf_io_cass import load_partial - load_partial(filename, dist_tree, hdf_filter, comm) - else: - from ._hdf_io_h5py import load_partial - load_partial(filename, dist_tree, hdf_filter) - -def write_partial(filename, dist_tree, hdf_filter, comm, legacy): - if legacy: - from ._hdf_io_cass import write_partial - else: - from ._hdf_io_h5py import write_partial - write_partial(filename, dist_tree, hdf_filter, comm) - -def write_tree(tree, filename, links=[], legacy=False): - """Sequential write to a CGNS file. - - Args: - tree (CGNSTree) : Tree to write - filename (str) : Path of the file - links (list) : List of links to create (see SIDS-to-Python guide) - - Example: - .. literalinclude:: snippets/test_io.py - :start-after: #write_tree@start - :end-before: #write_tree@end - :dedent: 2 - """ - if legacy: - from ._hdf_io_cass import write_full - else: - from ._hdf_io_h5py import write_full - write_full(filename, tree, links=links) - -def read_tree(filename, legacy=False): - """Sequential load of a CGNS file. - - Args: - filename (str) : Path of the file - Returns: - CGNSTree: Full (not distributed) CGNS tree - """ - if os.path.splitext(filename)[1] == '.yaml': - with open(filename, 'r') as f: - tree = parse_yaml_cgns.to_cgns_tree(f) - return tree - else: - if legacy: - from ._hdf_io_cass import read_full - else: - from ._hdf_io_h5py import read_full - return read_full(filename) - -def read_links(filename, legacy=False): - """Detect the links embedded in a CGNS file. - - Links information are returned as described in sids-to-python. Note that - no data are loaded and the tree structure is not even built. - - Args: - filename (str) : Path of the file - Returns: - list: Links description - """ - if legacy: - raise NotImplementedError("read_links is only available with legacy=False") - else: - from ._hdf_io_h5py import read_links - return read_links(filename) - -def load_tree_from_filter(filename, dist_tree, comm, hdf_filter, legacy): - """ - """ - hdf_filter_with_dim = {key: value for (key, value) in hdf_filter.items() \ - if isinstance(value, (list, tuple))} - - load_partial(filename, dist_tree, hdf_filter_with_dim, comm, legacy) - - # > Match with callable - hdf_filter_with_func = {key: value for (key, value) in hdf_filter.items() \ - if not isinstance(value, (list, tuple))} - unlock_at_least_one = True - while(len(hdf_filter_with_func) > 0 and unlock_at_least_one ): - # Update if you can - next_hdf_filter = dict() - unlock_at_least_one = False - for key, f in hdf_filter_with_func.items(): - try: - f(next_hdf_filter) - unlock_at_least_one = True - except RuntimeError: # Not ready yet - pass - - load_partial(filename, dist_tree, next_hdf_filter, comm, legacy) - - hdf_filter_with_func = {key: value for (key, value) in next_hdf_filter.items() \ - if not isinstance(value, (list, tuple))} - - if(unlock_at_least_one is False): - raise RuntimeError("Something strange in the loading process") - - n_shifted = ensure_PE_global_indexing(dist_tree) - if n_shifted > 0 and comm.Get_rank() == 0: - mlog.warning(f"Some NGon/ParentElements have been shift to be CGNS compliant") - n_shifted = ensure_signed_nface_connectivity(dist_tree, comm) - if n_shifted > 0 and comm.Get_rank() == 0: - mlog.warning(f"Some NFace/ElementConnectivity have been updated to be CGNS compliant") - -def save_tree_from_filter(filename, dist_tree, comm, hdf_filter, legacy): - """ - """ - hdf_filter_with_dim = {key: value for (key, value) in hdf_filter.items() if isinstance(value, list)} - hdf_filter_with_func = {key: value for (key, value) in hdf_filter.items() if not isinstance(value, list)} - - next_hdf_filter = dict() - for key, f in hdf_filter_with_func.items(): - f(hdf_filter_with_dim) - - #Dont save distribution info, but work on a copy to keep it for further use - saving_dist_tree = PT.shallow_copy(dist_tree) - clean_distribution_info(saving_dist_tree) - - write_partial(filename, saving_dist_tree, hdf_filter_with_dim, comm, legacy) - -def fill_size_tree(tree, filename, comm, legacy=False): - add_distribution_info(tree, comm) - hdf_filter = create_tree_hdf_filter(tree) - # Coords#Size appears in dict -> remove it - hdf_filter = {key:val for key,val in hdf_filter.items() if not key.endswith('#Size')} - - load_tree_from_filter(filename, tree, comm, hdf_filter, legacy) - PT.rm_nodes_from_name(tree, '*#Size') - - -def file_to_dist_tree(filename, comm, legacy=False): - """Distributed load of a CGNS file. - - Args: - filename (str) : Path of the file - comm (MPIComm) : MPI communicator - Returns: - CGNSTree: Distributed CGNS tree - """ - mlog.info(f"Distributed read of file {filename}...") - start = time.time() - filename = str(filename) - if os.path.splitext(filename)[1] == '.yaml': - if comm.Get_rank() == 0: - with open(filename, 'r') as f: - tree = parse_yaml_cgns.to_cgns_tree(f) - _enforce_pdm_dtype(tree) - else: - tree = None - dist_tree = full_to_dist.full_to_dist_tree(tree, comm, owner=0) - - else: - dist_tree = load_size_tree(filename, comm, legacy) - fill_size_tree(dist_tree, filename, comm, legacy) - - end = time.time() - dt_size = sum(MT.metrics.dtree_nbytes(dist_tree)) - all_dt_size = comm.allreduce(dt_size, MPI.SUM) - mlog.info(f"Read completed ({end-start:.2f} s) --" - f" Size of dist_tree for current rank is {mlog.bsize_to_str(dt_size)}" - f" (Σ={mlog.bsize_to_str(all_dt_size)})") - return dist_tree - -def dist_tree_to_file(dist_tree, filename, comm, legacy=False): - """Distributed write to a CGNS file. - - Args: - dist_tree (CGNSTree) : Distributed tree to write - filename (str) : Path of the file - comm (MPIComm) : MPI communicator - """ - dt_size = sum(MT.metrics.dtree_nbytes(dist_tree)) - all_dt_size = comm.allreduce(dt_size, MPI.SUM) - mlog.info(f"Distributed write of a {mlog.bsize_to_str(dt_size)} dist_tree" - f" (Σ={mlog.bsize_to_str(all_dt_size)})...") - start = time.time() - filename = str(filename) - hdf_filter = create_tree_hdf_filter(dist_tree) - save_tree_from_filter(filename, dist_tree, comm, hdf_filter, legacy) - end = time.time() - mlog.info(f"Write completed [{filename}] ({end-start:.2f} s)") - -def write_trees(tree, filename, comm, legacy=False): - """Sequential write to CGNS files. - - Write separate trees for each process. Rank id will be automatically - inserted in the filename. - - Args: - tree (CGNSTree) : Tree to write - filename (str) : Path of the file - comm (MPIComm) : MPI communicator - - Example: - .. literalinclude:: snippets/test_io.py - :start-after: #write_trees@start - :end-before: #write_trees@end - :dedent: 2 - """ - # Give to each process a filename - base_name, extension = os.path.splitext(filename) - base_name += f"_{comm.Get_rank()}" - _filename = base_name + extension - write_tree(tree, _filename, links=[], legacy=legacy) diff --git a/maia/io/distribution_tree.py b/maia/io/distribution_tree.py deleted file mode 100644 index 4dce0a2a..00000000 --- a/maia/io/distribution_tree.py +++ /dev/null @@ -1,124 +0,0 @@ -import numpy as np -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.utils import par_utils - -def interpret_policy(policy, comm): - if policy == 'gather': - policy = 'gather.0' - policy_split = policy.split('.') - assert len(policy_split) in [1, 2] - - policy_type = policy_split[0] - - if policy_type == "uniform": - distribution = par_utils.uniform_distribution - elif policy_type == "gather": - assert len(policy_split) == 2 - i_rank = int(policy_split[1]) - assert i_rank < comm.Get_size() - distribution = lambda n_elt, comm : par_utils.gathering_distribution(i_rank, n_elt, comm) - else: - raise ValueError("Unknown policy for distribution") - - return distribution - -def compute_subset_distribution(node, comm, distri_func): - """ - Compute the distribution for a given node using its PointList or PointRange child - If a PointRange node is found, the total lenght is getted from the product - of the differences for each direction (cgns convention (cgns convention : - first and last are included). - If a PointList node is found, the total lenght is getted from the product of - PointList#Size arrays, which store the size of the PL in each direction. - """ - - pr_n = PT.get_child_from_name(node, 'PointRange') - pl_n = PT.get_child_from_name(node, 'PointList') - - if(pr_n): - assert pl_n is None - pr_lenght = PT.PointRange.n_elem(pr_n) - MT.newDistribution({'Index' : distri_func(pr_lenght, comm)}, parent=node) - - if(pl_n): - assert pr_n is None - pls_n = PT.get_child_from_name(node, 'PointList#Size') - pl_size = PT.get_value(pls_n)[1] - MT.newDistribution({'Index' : distri_func(pl_size, comm)}, parent=node) - -def compute_connectivity_distribution(node): - """ - Once ESO is loaded, update element distribution with ElementConnectivity array - """ - eso_n = PT.get_child_from_name(node, 'ElementStartOffset') - if eso_n is None: - raise RuntimeError - size_n = PT.get_child_from_name(node, 'ElementConnectivity#Size') - - beg = eso_n[1][0] - end = eso_n[1][-1] - size = size_n[1][0] - - distri_n = MT.getDistribution(node) - dtype = PT.get_child_from_name(distri_n, 'Element')[1].dtype - PT.new_DataArray("ElementConnectivity", value=np.array([beg,end,size], dtype), parent=distri_n) - - -def compute_elements_distribution(zone, comm, distri_func): - """ - """ - for elt in PT.iter_children_from_label(zone, 'Elements_t'): - MT.newDistribution({'Element' : distri_func(PT.Element.Size(elt), comm)}, parent=elt) - eso_n = PT.get_child_from_name(elt, 'ElementStartOffset') - if eso_n is not None and eso_n[1] is not None: - compute_connectivity_distribution(elt) - -def compute_zone_distribution(zone, comm, distri_func): - """ - """ - zone_distri = {'Vertex' : distri_func(PT.Zone.n_vtx(zone), comm), - 'Cell' : distri_func(PT.Zone.n_cell(zone), comm)} - if PT.Zone.Type(zone) == 'Structured': - if PT.Zone.CellSize(zone).size == 3: - zone_distri['Face'] = distri_func(PT.Zone.n_face(zone), comm) - - MT.newDistribution(zone_distri, parent=zone) - - compute_elements_distribution(zone, comm, distri_func) - - predicate_list = [ - [lambda n : PT.get_label(n) in ['ZoneSubRegion_t', 'FlowSolution_t', 'DiscreteData_t']], - 'ZoneBC_t/BC_t', - 'ZoneBC_t/BC_t/BCDataSet_t', - ['ZoneGridConnectivity_t', lambda n: PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t']] - ] - - for predicate in predicate_list: - for node in PT.iter_children_from_predicates(zone, predicate): - compute_subset_distribution(node, comm, distri_func) - - -def add_distribution_info(dist_tree, comm, distribution_policy='uniform'): - """ - """ - distri_func = interpret_policy(distribution_policy, comm) - for zone in PT.iter_all_Zone_t(dist_tree): - compute_zone_distribution(zone, comm, distri_func) - -def clean_distribution_info(dist_tree): - """ - Remove the node related to distribution info from the dist_tree - """ - distri_name = ":CGNS#Distribution" - is_dist = lambda n : PT.get_label(n) in ['Elements_t', 'ZoneSubRegion_t', 'FlowSolution_t'] - is_gc = lambda n : PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] - for zone in PT.iter_all_Zone_t(dist_tree): - PT.rm_children_from_name(zone, distri_name) - for node in PT.iter_nodes_from_predicate(zone, is_dist): - PT.rm_children_from_name(node, distri_name) - for bc in PT.iter_nodes_from_predicates(zone, 'ZoneBC_t/BC_t'): - PT.rm_nodes_from_name(bc, distri_name, depth=2) - for gc in PT.iter_nodes_from_predicates(zone, ['ZoneGridConnectivity_t', is_gc]): - PT.rm_children_from_name(gc, distri_name) diff --git a/maia/io/file.cpp b/maia/io/file.cpp deleted file mode 100644 index 19e4134f..00000000 --- a/maia/io/file.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#if __cplusplus > 201703L -#include "maia/io/file.hpp" -#include "maia/utils/parallel/mpi4py.hpp" - - -#include "pybind11/embed.h" -namespace py = pybind11; -#include "cpp_cgns/interop/pycgns_converter.hpp" -#include "std_e/utils/embed_python.hpp" -#include "std_e/future/ranges.hpp" - - -namespace maia { - - -auto -file_to_dist_tree(const std::string& file_name, MPI_Comm comm) -> cgns::tree { - std_e::throw_if_no_python_interpreter(__func__); - auto m = py::module_::import("maia.io.cgns_io_tree"); - - auto mpi4py_comm = comm_to_mpi4py_comm(comm); - py::object py_tree = m .attr("file_to_dist_tree")(file_name,mpi4py_comm); - return cgns::to_cpp_tree_copy(py_tree); -} - - -} // maia -#endif // C++>17 diff --git a/maia/io/file.hpp b/maia/io/file.hpp deleted file mode 100644 index 7171ff91..00000000 --- a/maia/io/file.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - - -#include -#include "cpp_cgns/cgns.hpp" -#include - - -namespace maia { - - -auto file_to_dist_tree(const std::string& file_name, MPI_Comm comm) -> cgns::tree; - - -} // maia diff --git a/maia/io/fix_tree.py b/maia/io/fix_tree.py deleted file mode 100644 index e276013c..00000000 --- a/maia/io/fix_tree.py +++ /dev/null @@ -1,234 +0,0 @@ -from mpi4py import MPI -import numpy as np - -import maia.pytree as PT - -import maia -from maia.utils import np_utils, as_pdm_gnum, logging -from maia.algo.dist import matching_jns_tools as MJT - -def check_datasize(tree): - """ - Warns if a heavy array is not distributed - """ - is_heavy_node = lambda n: PT.get_label(n) in ['DataArray_t', 'IndexArray_t'] \ - and n[1] is not None and n[1].size > 100 - heavy_arrays = [PT.get_name(node) for node in \ - PT.iter_nodes_from_predicate(tree, is_heavy_node, explore='deep')] - - if heavy_arrays: - logging.warning(f"Some heavy data are not distributed: {heavy_arrays}") - -def fix_zone_datatype(size_tree, size_data): - """ - Cassiopee always read zones as int32. Fix it if input type was int64 - """ - for zone_path in PT.predicates_to_paths(size_tree, "CGNSBase_t/Zone_t"): - if size_data["/" + zone_path][1] == 'I8': - zone = PT.get_node_from_path(size_tree, zone_path) - zone[1] = zone[1].astype(np.int64) - -def fix_point_ranges(size_tree): - """ - Permute start and end of PointRange or PointRangeDonor nodes found in GridConnectivity1to1_t - in order to - a. be consistent with the transform node - b. keep the symmetry PR|a->b = PRDonor|b->a - """ - permuted = False - gc_t_path = 'CGNSBase_t/Zone_t/ZoneGridConnectivity_t/GridConnectivity1to1_t' - for base, zone, zgc, gc in PT.iter_children_from_predicates(size_tree, gc_t_path, ancestors=True): - base_name = PT.get_name(base) - zone_name = PT.get_name(zone) - gc_path = base_name + '/' + zone_name - # WARNING: for hybrid case structured zone could have PointList, PointListDonor. - if PT.get_child_from_label(gc, 'IndexRange_t') is not None: - transform = PT.GridConnectivity.Transform(gc) - point_range = PT.get_value(PT.get_child_from_name(gc, 'PointRange')) - point_range_d = PT.get_value(PT.get_child_from_name(gc, 'PointRangeDonor')) - - donor_dir = abs(transform) - 1 - nb_points = point_range[:,1] - point_range[:,0] - nb_points_d = np.sign(transform)*(point_range_d[donor_dir,1] - point_range_d[donor_dir,0]) - dir_to_swap = (nb_points != nb_points_d) - - if dir_to_swap.any(): - permuted = True - if MJT.gc_is_reference(gc, gc_path): - - opp_dir_to_swap = np.empty_like(dir_to_swap) - opp_dir_to_swap[donor_dir] = dir_to_swap - - point_range_d[opp_dir_to_swap, 0], point_range_d[opp_dir_to_swap, 1] = \ - point_range_d[opp_dir_to_swap, 1], point_range_d[opp_dir_to_swap, 0] - else: - point_range[dir_to_swap, 0], point_range[dir_to_swap, 1] = \ - point_range[dir_to_swap, 1], point_range[dir_to_swap, 0] - - assert (point_range_d[:,1] == PT.utils.gc_transform_point(gc, point_range[:,1])).all() - if permuted: - logging.warning(f"Some GridConnectivity1to1_t PointRange have been swapped because Transform specification was invalid") - -def ensure_symmetric_gc1to1(tree): - """ - Force structured GC1to1 to have symmetric PointRange/PointRangeDonor - This is done because some maia functions (as partitionning) require this condition, - but we should correct theses to not rely anymore on this assumption (TODO). - - """ - some_switched = False - - # To be less expansive (in jn matching process) we work on a shallow copy having only 1to1 GC_t - _tree = PT.shallow_copy(tree) - PT.rm_nodes_from_label(_tree, 'GridConnectivity_t') - - MJT.add_joins_donor_name(_tree, MPI.COMM_SELF) - matching_jns = MJT.get_matching_jns(_tree) - for jn_path, jn_path_opp in matching_jns: - jn = PT.get_node_from_path(tree, jn_path) - jn_opp = PT.get_node_from_path(tree, jn_path_opp) - - if PT.get_label(jn) == PT.get_label(jn_opp) == 'GridConnectivity1to1_t': - gc1_pr = PT.get_child_from_name(jn, 'PointRange') - gc1_prd = PT.get_child_from_name(jn, 'PointRangeDonor') - gc2_pr = PT.get_child_from_name(jn_opp, 'PointRange') - gc2_prd = PT.get_child_from_name(jn_opp, 'PointRangeDonor') - - if not (np.array_equal(PT.get_value(gc1_pr), PT.get_value(gc2_prd)) and - np.array_equal(PT.get_value(gc2_pr), PT.get_value(gc1_prd))): - PT.set_value(gc2_pr, PT.get_value(gc1_prd)) - PT.set_value(gc2_prd, PT.get_value(gc1_pr)) - some_switched = True - - if some_switched: - logging.warning(f"Some GridConnectivity1to1_t PointRange have been swapped to enforce symmetry with donor") - -def add_missing_pr_in_bcdataset(tree): - """ - When the GridLocation values of BC and BCDataSet are respectively 'Vertex' and '*FaceCenter', - if the PointRange is not given in the BCDataSet, the function compute it - Remark : if the shape of DataArrays in BCDataSet is coherent with a '*FaceCenter' GridLocation - but the GridLocation node is not defined, this function does not add the PointRange - """ - pr_added = False - bc_t_path = 'CGNSBase_t/Zone_t/ZoneBC_t/BC_t' - for base, zone, zbc, bc in PT.iter_children_from_predicates(tree, bc_t_path, ancestors=True): - if PT.get_value(PT.get_child_from_label(zone, 'ZoneType_t')) == 'Unstructured': - continue - if PT.get_child_from_label(bc, 'BCDataSet_t') is None: - continue - bc_grid_location = PT.Subset.GridLocation(bc) - bc_point_range = PT.get_value(PT.get_child_from_name(bc, 'PointRange')) - for bcds in PT.get_children_from_label(bc, 'BCDataSet_t'): - if PT.get_child_from_name(bcds, 'PointRange') is not None: - continue - bcds_grid_location = PT.Subset.GridLocation(bcds) - if not (bcds_grid_location.endswith('FaceCenter') and bc_grid_location == 'Vertex'): - continue - face_dir = PT.Subset.normal_axis(bc) - bcds_point_range = bc_point_range.copy(order='F') - bcds_point_range[:,1] -= 1 - bcds_point_range[face_dir,1] = bcds_point_range[face_dir,0] - new_pr = PT.new_IndexRange(value=bcds_point_range, parent=bcds) - pr_added = True - # logging.warning(f"Warning -- PointRange has been added on BCDataSet {zone[0]}/{bc[0]}/{bcds[0]}" - # " since data shape was no consistent with BC PointRange") - if pr_added: - logging.warning(f"PointRange has been added on some BCDataSet nodes because data size was inconsistent") - -def _enforce_pdm_dtype(tree): - """ - Convert the index & connectivity arrays to expected pdm_g_num_t - TODO : find better pattern for the "Subset iterator" and factorize it - """ - for zone in PT.get_all_Zone_t(tree): - zone[1] = as_pdm_gnum(zone[1]) - for elmt in PT.iter_children_from_label(zone, 'Elements_t'): - for name in ['ElementRange', 'ElementConnectivity', 'ElementStartOffset', 'ParentElements']: - node = PT.get_child_from_name(elmt, name) - if node: - node[1] = as_pdm_gnum(node[1]) - for pl in PT.iter_nodes_from_label(zone, 'IndexArray_t'): - pl[1] = as_pdm_gnum(pl[1]) - -def ensure_PE_global_indexing(dist_tree): - """ - This function ensures that the ParentElements array of the NGonElements, if existing, - is compliant with the CGNS standard ie refers faces using absolute numbering. - This function works under the following assumptions (which could be released, - but also seems to be imposed by the standard) - - At most one NGonElements node exists - - NGonElements and standard elements can not be mixed together - """ - n_shifted = 0 - for zone in PT.get_all_Zone_t(dist_tree): - elts = PT.get_children_from_label(zone, 'Elements_t') - ngon_nodes = [elt for elt in elts if PT.Element.CGNSName(elt)=='NGON_n'] - oth_nodes = [elt for elt in elts if PT.Element.CGNSName(elt)!='NGON_n'] - if ngon_nodes == []: - continue - elif len(ngon_nodes) == 1: - if PT.get_child_from_name(ngon_nodes[0], 'ParentElements') is None: # Skip next checks to allow 2D zones with NGON & BAR - continue - if len(oth_nodes) > 1 or (len(oth_nodes) == 1 and PT.Element.CGNSName(oth_nodes[0]) != 'NFACE_n'): - raise RuntimeError(f"Zone {PT.get_name(zone)} has both NGon and Std elements nodes, which is not supported") - else: - raise RuntimeError(f"Multiple NGon nodes found in zone {PT.get_name(zone)}") - - ngon_n = ngon_nodes[0] - ngon_pe_n = PT.get_child_from_name(ngon_n, 'ParentElements') - if ngon_pe_n: - n_faces = PT.Element.Size(ngon_n) - ngon_pe = ngon_pe_n[1] - if PT.Element.Range(ngon_n)[0] == 1 and ngon_pe.shape[0] > 0 and ngon_pe[0].max() <= n_faces: - np_utils.shift_nonzeros(ngon_pe, n_faces) - n_shifted += 1 - - return n_shifted - -def ensure_signed_nface_connectivity(dist_tree, comm): - """ - Check if negative indices appears in NFace connectivity; it should be the case. - If not, remove the NFace node and recreate it from NGon/ParentElements - """ - n_fixed = 0 - for zone in PT.get_all_Zone_t(dist_tree): - if PT.Zone.has_nface_elements(zone): - nface = PT.Zone.NFaceNode(zone) - nface_ec = PT.get_child_from_name(nface, 'ElementConnectivity')[1] - is_signed = nface_ec.size == 0 or np.any(nface_ec < 0) - if PT.Element.Size(nface) > 1 and not comm.allreduce(is_signed, MPI.LAND): - PT.rm_child(zone, nface) - maia.algo.pe_to_nface(zone, comm) - n_fixed += 1 - return n_fixed - -def rm_legacy_nodes(tree): - eh_paths = PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t/:elsA#Hybrid') - if len(eh_paths) > 0: - logging.warning(f"Legacy nodes ':elsA#Hybrid' skipped when reading file") - for eh_path in eh_paths: - PT.rm_node_from_path(tree, eh_path) - - arrays_removed = False - for zone in PT.iter_all_Zone_t(tree): - for fs in PT.iter_nodes_from_label(zone, 'FlowSolution_t'): - all_arrays = PT.get_names(PT.get_children_from_label(fs, 'DataArray_t')) - size_arrays = [name for name in all_arrays if name.endswith('#Size')] - has_no_size = lambda n: not PT.get_name(n).endswith('#Size') and f'{PT.get_name(n)}#Size' not in size_arrays - - n_child_bck = len(PT.get_children(fs)) - PT.rm_children_from_predicate(fs, lambda n: PT.get_label(n) == 'DataArray_t' and has_no_size(n)) - arrays_removed = arrays_removed or len(PT.get_children(fs)) < n_child_bck - if arrays_removed: - logging.warning(f"Some empty arrays under FlowSolution_t nodes have been skipped when reading file") - -def corr_index_range_names(tree): - corr = False - for zone in PT.get_all_Zone_t(tree): - for bc in PT.get_nodes_from_label(zone, 'BC_t'): - for er_n in PT.get_children_from_name_and_label(bc, 'ElementRange', 'IndexRange_t'): - PT.set_name(er_n, 'PointRange') - corr = True - if corr: - logging.warning(f"Some IndexRange_t nodes under BC_t nodes have been renamed ('ElementRange' becomes 'PointRange').") diff --git a/maia/io/hdf/_hdf_cgns.py b/maia/io/hdf/_hdf_cgns.py deleted file mode 100644 index d12b0ad9..00000000 --- a/maia/io/hdf/_hdf_cgns.py +++ /dev/null @@ -1,399 +0,0 @@ -import numpy as np -import h5py -from h5py import h5, h5a, h5d, h5f, h5g, h5p, h5s, h5t, h5o - -from maia.pytree.graph import algo -from maia.pytree.graph.utils import list_iterator - -C33_t = h5t.C_S1.copy() -C33_t.set_size(33) -C3_t = h5t.C_S1.copy() -C3_t.set_size(3) - -DTYPE_TO_CGNSTYPE = {'int8' : 'B1', - 'int32' : 'I4', - 'int64' : 'I8', - 'float32' : 'R4', - 'float64' : 'R8', - 'bytes8' : 'C1'} - -class AttributeRW: - """ A singleton class usefull to read & write hdf attribute w/ allocating buffers """ - - def __new__(cls): - if not hasattr(cls, 'instance'): - cls.instance = super(AttributeRW, cls).__new__(cls) - cls.buff_S33 = np.empty(1, '|S33') - cls.buff_S3 = np.empty(1, '|S3') - cls.buff_flag1 = np.array([1], np.int32) - return cls.instance - - def read_str_33(self, gid, attr_name): - """ Read the attribute attr_name in the object gid and return it - as a sttriped string. """ - _name = h5a.open(gid, attr_name) - _name.read(self.buff_S33) - return self.buff_S33.tobytes().decode().rstrip('\x00') ###UGLY - - def read_bytes_3(self, gid, attr_name): - """ Read the attribute attr_name in the object gid and return it - as bytes. """ - _name = h5a.open(gid, attr_name) - _name.read(self.buff_S3) - return self.buff_S3[0] - - def write_str_3(self, gid, attr_name, attr_value): - """ Create the attribute attr_name within the object gid and - write attr_value (of size <=2) inside. """ - self.buff_S3[0] = attr_value - space = h5s.create(h5s.SCALAR) - attr_id = h5a.create(gid, attr_name, C3_t, space) - attr_id.write(self.buff_S3) - def write_str_33(self, gid, attr_name, attr_value): - """ Create the attribute attr_name within the object gid and - write attr_value (of size <=32) inside. """ - self.buff_S33[0] = attr_value - space = h5s.create(h5s.SCALAR) - attr_id = h5a.create(gid, attr_name, C33_t, space) - attr_id.write(self.buff_S33) - - def write_flag(self, gid): - """ Create and fill the 'flags' attribute within the object gid. """ - space = h5s.create_simple((1,)) - attr_id = h5a.create(gid, b'flags', h5t.NATIVE_INT32, space) - attr_id.write(self.buff_flag1) - -class HDF5GraphAdaptor: - """ A class exposing the 'graph interface' for hdf files in order - to use graph iterators """ - def __init__(self, root_id): - self.root_id = root_id - def root_iterator(self) -> list_iterator: - return iter([self.root_id]) - def child_iterator(self, node_id) -> list_iterator: - return (h5g.open(node_id, child_name) for child_name in node_id \ - if h5o.get_info(node_id, child_name).type == h5o.TYPE_GROUP) - - -def knows_crt_order(gid): - """ Return True if links have been added with CRT_ORDER_INDEXED prop """ - ordering_flags = gid.get_create_plist().get_link_creation_order() - return ordering_flags & h5p.CRT_ORDER_INDEXED == h5p.CRT_ORDER_INDEXED - -def is_combinated(dataspace): - """ Check if the provided dataspace if combinated or not. """ - return not (len(dataspace) == 10) - -def group_by(iterable, n): - """ Iterate chunk by chunk (of size n) over an iterable object : - s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), ... """ - #https://stackoverflow.com/questions/5389507/iterating-over-every-two-elements-in-a-list - return zip(*[iter(iterable)] * n) - -def add_root_attributes(rootid): - """ Write the attributes of the CGNS-HDF root node. """ - - attr_writter = AttributeRW() - attr_writter.write_str_33(rootid, b'name', 'HDF5 MotherNode') - attr_writter.write_str_33(rootid, b'label', 'Root Node of HDF5 File') - attr_writter.write_str_3 (rootid, b'type' , 'MT') - - if h5t.NATIVE_FLOAT == h5t.IEEE_F32BE: - format = 'IEEE_BIG_32' - elif h5t.NATIVE_FLOAT == h5t.IEEE_F32LE: - format = 'IEEE_LITTLE_32' - elif h5t.NATIVE_FLOAT == h5t.IEEE_F64BE: - format = 'IEEE_BIG_64' - elif h5t.NATIVE_FLOAT == h5t.IEEE_F64LE: - format = 'IEEE_LITTLE_64' - version = 'HDF5 Version {}.{}.{}'.format(*h5.get_libversion()) - - # Those two are described at attributes in cgns doc, but the C lib - # looks for dataset (see https://cgnsorg.atlassian.net/browse/CGNS-283) - buffer = np.zeros(33, dtype='c') - for i,c in enumerate(format): - buffer[i] = c - write_data(rootid, buffer, dataset_name=b' format') - buffer[:] = '\0' - for i,c in enumerate(version): - buffer[i] = c - write_data(rootid, buffer, dataset_name=b' hdf5version') - -def open_from_path(fid, path, follow_links=True): - """ Return the hdf node registred at the specified path in the file fid. """ - attr_reader = AttributeRW() - gid = h5g.open(fid, b'/') - for name in path.split('/'): - gid = h5g.open(gid, name.encode()) - if follow_links and attr_reader.read_bytes_3(gid, b'type') == b'LK': #Follow link - gid = h5g.open(gid, b' link') - return gid - -def _select_file_slabs(hdf_space, filter): - """ Performs the 'select_hyperslab' operation on a open hdf_dataset space, - using the input filter. - Filter must be a list of 4 elements (start, stride, count, block) - and can be combinated. """ - if is_combinated(filter): - src_filter = filter[4] - for i, src_filter_i in enumerate(group_by(src_filter, 4)): #Iterate 4 by 4 on the combinated slab - src_start, src_stride, src_count, src_block = [tuple(src_filter_i[i][::-1]) for i in range(4)] - op = h5s.SELECT_SET if i == 0 else h5s.SELECT_OR - hdf_space.select_hyperslab(src_start, src_count, src_stride, src_block, op=op) - else: - src_start, src_stride, src_count, src_block = [tuple(filter[i][::-1]) for i in range(4,8)] - hdf_space.select_hyperslab(src_start, src_count, src_stride, src_block) - -def _create_mmry_slabs(filter): - """ Create and return a memory dataspace from a filter object. - Filter must be a list of 4 elements (start, stride, count, block). - Filter can no be combinated. """ - dst_start, dst_stride, dst_count, dst_block = [tuple(filter[i][::-1]) for i in range(0,4)] - m_dspace = h5s.create_simple(dst_count) - m_dspace.select_hyperslab(dst_start, dst_count, dst_stride, dst_block) - return m_dspace - -def load_data(gid): - """ Create a numpy array from the dataset stored in the hdf node gid, - reading all data (no hyperslab). - HDFNode must have data (type != MT). - Numpy array is reshaped to F order **but** kind is not converted. """ - hdf_dataset = h5d.open(gid, b' data') - - shape = hdf_dataset.shape[::-1] - - array = np.empty(shape, hdf_dataset.dtype, order='F') - array_view = array.T - hdf_dataset.read(h5s.ALL, h5s.ALL, array_view) - - return array - -def load_data_partial(gid, filter): - """ Create a numpy array from the dataset stored in the hdf node gid, - reading partial data (using global filter object). - HDFNode must have data (type != MT). - Numpy array is reshaped to F order **but** kind is not converted. """ - hdf_dataset = h5d.open(gid, b' data') - - # Prepare dataspaces - hdf_space = hdf_dataset.get_space() - _select_file_slabs(hdf_space, filter) - m_dspace = _create_mmry_slabs(filter) - - array = np.empty(m_dspace.shape[::-1], hdf_dataset.dtype, order='F') - array_view = array.T - hdf_dataset.read(m_dspace, hdf_space, array_view) - - return array - - -def write_data(gid, array, dataset_name=b' data'): - """ Write a dataset on node gid from a numpy array, - dumping all data (no hyperslab). """ - array_view = array.T - if array_view.dtype == 'S1': - array_view.dtype = np.int8 - - space = h5s.create_simple(array_view.shape) - data = h5d.create(gid, dataset_name, h5t.py_create(array_view.dtype), space) - data.write(h5s.ALL, h5s.ALL, array_view) - -def write_data_partial(gid, array, filter): - """ Write a dataset on node gid from a numpy array, - using hyperslabls (from filter object). """ - glob_dims = tuple(filter[-2][::-1]) - - # Prepare dataspaces - hdf_space = h5s.create_simple(glob_dims) - _select_file_slabs(hdf_space, filter) - m_dspace = _create_mmry_slabs(filter) - - array_view = array.T - if array_view.dtype == 'S1': - array_view.dtype = np.int8 - data = h5d.create(gid, b' data', h5t.py_create(array_view.dtype), hdf_space) - xfer_plist = h5p.create(h5p.DATASET_XFER) - xfer_plist.set_dxpl_mpio(h5py.h5fd.MPIO_INDEPENDENT) - data.write(m_dspace, hdf_space, array_view, dxpl=xfer_plist) - -def write_link(gid, node_name, target_file, target_node): - """ Create a linked child named node_name under the open parent node gid - Child links to the node target_node (absolute path) of file target_file. """ - node_id = h5g.create(gid, node_name.encode()) - - attr_writter = AttributeRW() - attr_writter.write_str_33(node_id, b'name', node_name) - attr_writter.write_str_33(node_id, b'label', '') - attr_writter.write_str_3 (node_id, b'type', 'LK') - - write_data(node_id, np.array(tuple(target_file+'\0'), 'S1'), b' file') - write_data(node_id, np.array(tuple(target_node+'\0'), 'S1'), b' path') - - node_id.links.create_external(" link".encode(), target_file.encode(), target_node.encode()) - -def _load_node_partial(gid, parent, load_if, ancestors_stack): - """ Internal recursive implementation for load_tree_partial. """ - - attr_reader = AttributeRW() - name = attr_reader.read_str_33(gid, b'name') - label = attr_reader.read_str_33(gid, b'label') - b_kind = attr_reader.read_bytes_3(gid, b'type') - value = None - - if b_kind == b'LK': #Follow link - gid = h5g.open(gid, b' link') - b_kind = attr_reader.read_bytes_3(gid, b'type') - # Label may be empty in original node and present only in linked node - label = attr_reader.read_str_33(gid, b'label') - - ancestors_stack[0].append(name) - ancestors_stack[1].append(label) - - if load_if(*ancestors_stack): - if b_kind != b'MT': - value = load_data(gid) - if b_kind==b'C1': - value.dtype = 'S1' - elif b_kind != b'MT': - _data = h5d.open(gid, b' data') - size_node = [name + '#Size', - np.array(_data.shape[::-1]), - [], - 'DataArray_t'] - parent[2].append(size_node) - - pynode = [name, value, [], label] - parent[2].append(pynode) - - # Define the function that will be applied to the child of the current hdf node - # thought iterate : we just start next recursion level if child is not a dataset - iter_func = lambda n : _load_node_partial(h5g.open(gid, n), pynode, load_if, ancestors_stack) \ - if h5o.get_info(gid, n).type == h5o.TYPE_GROUP else None - - idx_type = h5.INDEX_CRT_ORDER if knows_crt_order(gid) else h5.INDEX_NAME - gid.links.iterate(iter_func, idx_type=idx_type) - ancestors_stack[0].pop() - ancestors_stack[1].pop() - - -def _write_node_partial(gid, node, write_if, ancestors_stack): - """ Internal recursive implementation for write_tree_partial. """ - - cgtype = 'MT' if node[1] is None else DTYPE_TO_CGNSTYPE[node[1].dtype.name] - ancestors_stack[0].append(node[0]) - ancestors_stack[1].append(node[3]) - - gc_pl = h5p.create(h5p.GROUP_CREATE) - gc_pl.set_link_creation_order(h5p.CRT_ORDER_TRACKED | h5p.CRT_ORDER_INDEXED) - - node_id = h5g.create(gid, node[0].encode(), gcpl=gc_pl) - - # Write attributes - attr_writter = AttributeRW() - attr_writter.write_str_33(node_id, b'name', node[0]) - attr_writter.write_str_33(node_id, b'label', node[3]) - attr_writter.write_str_3 (node_id, b'type', cgtype) - attr_writter.write_flag(node_id) - - if write_if(*ancestors_stack) and node[1] is not None: - write_data(node_id, node[1]) - - # Write children - for child in node[2]: - _write_node_partial(node_id, child, write_if, ancestors_stack) - ancestors_stack[0].pop() - ancestors_stack[1].pop() - - -def load_tree_partial(filename, load_predicate): - """ - Create a pyCGNS tree from the (partial) read of an hdf file. - - For each encountered node, the name and label of node is registered in tree, - then the load_predicate is evaluated : - - if load_predicate return True, the data of the node is fully loaded - and registered in tree - - if load_predicate return False, the data is skipped, buts its shape is registered in - tree as the value of an additional node of name name Node#Size - - Note : if load_predicate returns always True, the tree is then fully read. - """ - tree = ['CGNSTree', None, [], 'CGNSTree_t'] - - fid = h5f.open(bytes(filename, 'utf-8'), h5f.ACC_RDONLY) - rootid = h5g.open(fid, b'/') - - iter_func = lambda n : _load_node_partial(h5g.open(rootid, n), tree, load_predicate, ([],[])) \ - if h5o.get_info(rootid, n).type == h5o.TYPE_GROUP else None - idx_type = h5.INDEX_CRT_ORDER if knows_crt_order(rootid) else h5.INDEX_NAME - rootid.links.iterate(iter_func, idx_type=idx_type) - - fid.close() - return tree - - -def load_tree_links(filename): - """ Collect and return the links present in a CGNS File """ - - class LinkVisitor: - """ Graph visitor used to collect link information """ - def __init__(self): - self.attr_reader = AttributeRW() - self.links = [] - def pre(self, node_ids): - - gid = node_ids[-1] - b_kind = self.attr_reader.read_bytes_3(gid, b'type') - - if b_kind == b'LK': - #Target directory ; the CGNS norm is unclear about how a link should start, - # but other libraries are also doing that - link = ['.'] - for ds_name in [b' file', b' path']: #Target file, then target path - hdf_dataset = h5d.open(gid, ds_name) - shape = hdf_dataset.shape[::-1] - array = np.empty(shape, hdf_dataset.dtype, order='F') - array_view = array.T - hdf_dataset.read(h5s.ALL, h5s.ALL, array_view) - array.dtype = 'S1' - link.append(array.tobytes().decode().rstrip('\x00')) - path = '/'.join([self.attr_reader.read_str_33(id, b'name') for id in node_ids[1:]]) - link.append(path) #Current path - self.links.append(link) - return algo.step.over - - fid = h5f.open(bytes(filename, 'utf-8'), h5f.ACC_RDONLY) - rootid = h5g.open(fid, b'/') - - visitor = LinkVisitor() - algo.depth_first_search(HDF5GraphAdaptor(rootid), visitor, depth='all') - fid.close() - return visitor.links - -def write_tree_partial(tree, filename, write_predicate): - """ - Write a (partial) hdf file from a pyCGNS tree. - - For each encountered node, the name and label of node is registred in file, - then the write_predicate is evaluated : - - if write_predicate return True, the data of the CGNS is written in the file - - if write_predicate return False, the data is no written (meaning that the ' data' - dataset is not created; nevertheless, the attribute 'type' storing the - datakind is written) - - Note : if write_predicate returns always False, the tree is then fully writed. - """ - - fc_pl = h5p.create(h5p.FILE_CREATE) - fc_pl.set_link_creation_order(h5p.CRT_ORDER_TRACKED | h5p.CRT_ORDER_INDEXED) - fid = h5f.create(bytes(filename, 'utf-8'), fcpl=fc_pl) - - rootid = h5g.open(fid, b'/') - - # Write some attributes of root node - add_root_attributes(rootid) - for node in tree[2]: - _write_node_partial(rootid, node, write_predicate, ([],[])) - - fid.close() - diff --git a/maia/io/hdf/cgns_elements.py b/maia/io/hdf/cgns_elements.py deleted file mode 100644 index eb3eff8a..00000000 --- a/maia/io/hdf/cgns_elements.py +++ /dev/null @@ -1,110 +0,0 @@ -from functools import partial -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.io.distribution_tree import compute_connectivity_distribution -from .hdf_dataspace import create_pe_dataspace - -def gen_elemts(zone_tree): - elmts_ini = PT.get_children_from_label(zone_tree, 'Elements_t') - for elmt in elmts_ini: - yield elmt - -def load_element_connectivity_from_eso(elmt, zone_path, hdf_filter): - """ - """ - #If needed (reading), update distribution using ESO, which is now loaded - distrib = MT.getDistribution(elmt) - if PT.get_child_from_name(distrib, 'ElementConnectivity') is None: - compute_connectivity_distribution(elmt) - - distrib_ec = MT.getDistribution(elmt, "ElementConnectivity")[1] - dn_elmt_c = distrib_ec[1] - distrib_ec[0] - n_elmt_c = distrib_ec[2] - - DSMMRYEC = [[0 ], [1], [dn_elmt_c], [1]] - DSFILEEC = [[distrib_ec[0]], [1], [dn_elmt_c], [1]] - DSGLOBEC = [[n_elmt_c]] - DSFORMEC = [[0]] - - ec_path = zone_path+"/"+elmt[0]+"/ElementConnectivity" - hdf_filter[ec_path] = DSMMRYEC + DSFILEEC + DSGLOBEC + DSFORMEC - -def create_zone_eso_elements_filter(elmt, zone_path, hdf_filter, mode): - """ - """ - distrib_elmt = PT.get_value(MT.getDistribution(elmt, 'Element')) - dn_elmt = distrib_elmt[1] - distrib_elmt[0] - - # > For NGon only - pe = PT.get_child_from_name(elmt, 'ParentElements') - if(pe): - data_space = create_pe_dataspace(distrib_elmt) - hdf_filter[f"{zone_path}/{PT.get_name(elmt)}/ParentElements"] = data_space - if PT.get_child_from_name(elmt, 'ParentElementsPosition'): - hdf_filter[f"{zone_path}/{PT.get_name(elmt)}/ParentElementsPosition"] = data_space - - eso = PT.get_child_from_name(elmt, 'ElementStartOffset') - eso_path = None - if(eso): - # Distribution for NGon/NFace -> ElementStartOffset is the same than DistrbutionFace, except - # that the last proc have one more element - n_elmt = distrib_elmt[2] - if(mode == 'read'): - dn_elmt_idx = dn_elmt + 1 # + int(distrib_elmt[1] == n_elmt) - elif(mode == 'write'): - dn_elmt_idx = dn_elmt + int((distrib_elmt[1] == n_elmt) and (distrib_elmt[0] != distrib_elmt[1])) - DSMMRYESO = [[0 ], [1], [dn_elmt_idx], [1]] - DSFILEESO = [[distrib_elmt[0]], [1], [dn_elmt_idx], [1]] - DSGLOBESO = [[n_elmt+1]] - DSFORMESO = [[0]] - - eso_path = zone_path+"/"+elmt[0]+"/ElementStartOffset" - hdf_filter[eso_path] = DSMMRYESO + DSFILEESO + DSGLOBESO + DSFORMESO - - ec = PT.get_child_from_name(elmt, 'ElementConnectivity') - if ec: - if eso_path is None: - raise RuntimeError(f"Missing ElementStartOffset array for elements {PT.get_name(elmt)}" - f" (kind={PT.Element.CGNSName(elmt)}). Please convert your input file" - f" to CGNS4 standard using maia_poly_old_to_new.") - ec_path = zone_path+"/"+elmt[0]+"/ElementConnectivity" - hdf_filter[ec_path] = partial(load_element_connectivity_from_eso, elmt, zone_path) - -def create_zone_std_elements_filter(elmt, zone_path, hdf_filter): - """ - """ - distrib_elmt = PT.get_value(MT.getDistribution(elmt, 'Element')) - dn_elmt = distrib_elmt[1] - distrib_elmt[0] - - elmt_npe = PT.Element.NVtx(elmt) - - DSMMRYElmt = [[0 ], [1], [dn_elmt*elmt_npe], [1]] - DSFILEElmt = [[distrib_elmt[0]*elmt_npe], [1], [dn_elmt*elmt_npe], [1]] - DSGLOBElmt = [[distrib_elmt[2]*elmt_npe]] - DSFORMElmt = [[0]] - - path = zone_path+"/"+elmt[0]+"/ElementConnectivity" - hdf_filter[path] = DSMMRYElmt + DSFILEElmt + DSGLOBElmt + DSFORMElmt - - pe = PT.get_child_from_name(elmt, 'ParentElements') - if(pe): - data_space = create_pe_dataspace(distrib_elmt) - hdf_filter[f"{zone_path}/{PT.get_name(elmt)}/ParentElements"] = data_space - if PT.get_child_from_name(elmt, 'ParentElementsPosition'): - hdf_filter[f"{zone_path}/{PT.get_name(elmt)}/ParentElementsPosition"] = data_space - - -def create_zone_elements_filter(zone_tree, zone_path, hdf_filter, mode): - """ - Prepare the hdf_filter for all the Element_t nodes found in the zone. - """ - zone_elmts = gen_elemts(zone_tree) - for elmt in zone_elmts: - if PT.Element.CGNSName(elmt) in ['NGON_n', 'NFACE_n', 'MIXED']: - create_zone_eso_elements_filter(elmt, zone_path, hdf_filter, mode) - else: - create_zone_std_elements_filter(elmt, zone_path, hdf_filter) - diff --git a/maia/io/hdf/cgns_subsets.py b/maia/io/hdf/cgns_subsets.py deleted file mode 100644 index 547ed8ff..00000000 --- a/maia/io/hdf/cgns_subsets.py +++ /dev/null @@ -1,132 +0,0 @@ -import maia.pytree as PT -import maia.pytree.maia as MT - -from . import utils -from .hdf_dataspace import create_pointlist_dataspace, create_data_array_filter - -def _create_pl_filter(node, node_path, pl_name, distri_index, hdf_filter): - pl_node = PT.get_child_from_name(node, pl_name) - if pl_node is not None: - # Retrieve index to create filter - if pl_node[1] is not None: - idx_dim = pl_node[1].shape[0] - else: #When reading, PL are None -> catch from #Size node - idx_dim = PT.get_child_from_name(node, f'{pl_name}#Size')[1][0] - hdf_filter[f"{node_path}/{pl_name}"] = create_pointlist_dataspace(distri_index, idx_dim) - -def create_zone_bc_filter(zone, zone_path, hdf_filter): - """ - Fill up the hdf filter for the BC_t nodes present in - the zone. - Filter is created for the following nodes : - - PointList (if present = unstruct. only) - - All arrays founds in BCDataSets. Those arrays are supposed - to be shaped as the PointList array. If a BCDataSet contains - no PointList/PointRange node, the data is assumed to be consistent - with the PointList/PointRange of the BC. Otherwise, the PointList/ - PointRange node of the BCDataSet is used to set the size of the BCData - arrays. In this case, the PointList (if any) of the BCDataSet is - written in the filter as well. - """ - for zone_bc in PT.iter_children_from_label(zone, 'ZoneBC_t'): - zone_bc_path = zone_path+"/"+zone_bc[0] - for bc in PT.iter_children_from_label(zone_bc, 'BC_t'): - bc_path = zone_bc_path+"/"+bc[0] - - distrib_bc = PT.get_value(MT.getDistribution(bc, 'Index')) - - _create_pl_filter(bc, bc_path, 'PointList', distrib_bc, hdf_filter) - - for bcds in PT.iter_children_from_label(bc, "BCDataSet_t"): - bcds_path = bc_path + "/" + bcds[0] - distrib_bcds_n = MT.getDistribution(bcds) - - if distrib_bcds_n is None: #BCDS uses BC distribution - distrib_data = distrib_bc - else: #BCDS has its own distribution - distrib_data = PT.get_child_from_name(distrib_bcds_n, 'Index')[1] - _create_pl_filter(bcds, bcds_path, 'PointList', distrib_data, hdf_filter) - - # At read time, BCDataSet can be badly shaped (1,N) or (N1,N2) instead of (M,) - # We use the #Size node to reshape it - size_node = PT.get_child_from_name(bcds,'*#Size',depth=2) - data_shape = PT.get_value(size_node) if size_node else None - data_space_array = create_data_array_filter(distrib_data, data_shape) - - for bcdata in PT.iter_children_from_label(bcds, 'BCData_t'): - bcdata_path = bcds_path + "/" + bcdata[0] - utils.apply_dataspace_to_arrays(bcdata, bcdata_path, data_space_array, hdf_filter) - - -def create_zone_grid_connectivity_filter(zone, zone_path, hdf_filter): - """ - Fill up the hdf filter for the GC_t nodes present in the zone. - For unstructured GC (GridConnectivity_t), the filter is set up for - the PointList and PointListDonor arrays. - Structured GC (GridConnectivity1to1_t) are skipped since there is - no data to load for these nodes. - """ - for zone_gc in PT.iter_children_from_label(zone, 'ZoneGridConnectivity_t'): - zone_gc_path = zone_path+"/"+zone_gc[0] - for gc in PT.iter_children_from_label(zone_gc, 'GridConnectivity_t'): - gc_path = zone_gc_path+"/"+gc[0] - distrib_ia = PT.get_value(MT.getDistribution(gc, 'Index')) - _create_pl_filter(gc, gc_path, 'PointList', distrib_ia, hdf_filter) - _create_pl_filter(gc, gc_path, 'PointListDonor', distrib_ia, hdf_filter) - -def create_flow_solution_filter(zone, zone_path, hdf_filter): - """ - Fill up the hdf filter for the FlowSolution_t nodes present in the - zone. The size of the dataspace are computed from the pointList node - if present, or using allCells / allVertex if no pointList is present. - Filter is created for the arrays and for the PointList if present - """ - distrib_vtx = PT.get_value(MT.getDistribution(zone, 'Vertex')) - distrib_cell = PT.get_value(MT.getDistribution(zone, 'Cell')) - for flow_solution in PT.iter_children_from_label(zone, 'FlowSolution_t'): - flow_solution_path = zone_path + "/" + PT.get_name(flow_solution) - grid_location = PT.Subset.GridLocation(flow_solution) - distrib_ud_n = MT.getDistribution(flow_solution) - if distrib_ud_n: - distrib_data = PT.get_child_from_name(distrib_ud_n, 'Index')[1] - _create_pl_filter(flow_solution, flow_solution_path, 'PointList', distrib_data, hdf_filter) - data_space = create_data_array_filter(distrib_data) - elif(grid_location == 'CellCenter'): - data_space = create_data_array_filter(distrib_cell, zone[1][:,1]) - elif(grid_location == 'Vertex'): - data_space = create_data_array_filter(distrib_vtx, zone[1][:,0]) - else: - raise RuntimeError(f"GridLocation {grid_location} is not allowed without PL") - utils.apply_dataspace_to_arrays(flow_solution, flow_solution_path, data_space, hdf_filter) - -def create_zone_subregion_filter(zone, zone_path, hdf_filter): - """ - Fill up the hdf filter for the ZoneSubRegion_t nodes present in - the zone. - The size of the dataspace are computed from - - the corresponding BC or GC if the subregion is related to one of them - (this information is given by a BCRegionName or GridConnectivityRegionName - node) - - the PointList / PointSize node of the subregion otherwise. - Filter is created for the following nodes: - - All arrays present in ZoneSubRegion; - - PointList array if the zone is unstructured and if the subregion - is not related to a BC/GC. - """ - for zone_subregion in PT.iter_children_from_label(zone, 'ZoneSubRegion_t'): - zone_subregion_path = zone_path+"/"+zone_subregion[0] - - # Search matching region - matching_region_path = PT.Subset.ZSRExtent(zone_subregion, zone) - matching_region = PT.get_node_from_path(zone, matching_region_path) - assert(matching_region is not None) - - distrib_ud_n = MT.getDistribution(matching_region) - if not distrib_ud_n: - raise RuntimeError("ZoneSubRegion {0} is not well defined".format(zone_subregion[0])) - distrib_data = PT.get_child_from_name(distrib_ud_n, 'Index')[1] - - _create_pl_filter(zone_subregion, zone_subregion_path, 'PointList', distrib_data, hdf_filter) - - data_space_ar = create_data_array_filter(distrib_data) - utils.apply_dataspace_to_arrays(zone_subregion, zone_subregion_path, data_space_ar, hdf_filter) diff --git a/maia/io/hdf/hdf_dataspace.py b/maia/io/hdf/hdf_dataspace.py deleted file mode 100644 index f9ae3c17..00000000 --- a/maia/io/hdf/hdf_dataspace.py +++ /dev/null @@ -1,90 +0,0 @@ -from maia.utils.numbering.range_to_slab import compute_slabs - -def create_combined_dataspace(data_shape, distrib): - """ - Create a dataspace from a flat distribution, but for arrays having a 3d (resp. 2d) stucture - ie (Nx, Ny, Nz) (resp. (Nx, Ny)) numpy arrays. - First, the 1d distribution is converted into slabs to load with the function compute_slabs. - Those slabs are then combined to create the dataspace : - for DSFile, we are expecting a list including all the slabs looking like - [[startI_1, startJ_1, startK_1], [1,1,1], [nbI_1, nbJ_1, nbK_1], [1,1,1], - [startI_2, startJ_2, startK_2], [1,1,1], [nbI_2, nbJ_2, nbK_2], [1,1,1], ... - [startI_N, startJ_N, startK_N], [1,1,1], [nbI_N, nbJ_N, nbK_N], [1,1,1]] - DSGlob me be the list of the tree dimensions sizes - DSMmry and DSFrom have the same structure than flat / 1d dataspaces - - Mostly usefull for structured blocks. - """ - dim = len(data_shape) - slab_list = compute_slabs(data_shape, distrib[0:2]) - dn_da = distrib[1] - distrib[0] - DSFILEDA = [] - if len(slab_list) == 0: - if dim == 3: - DSFILEDA.extend([[0,0,0], [1,1,1], [0,0,0], [1,1,1]]) - else: - DSFILEDA.extend([[0,0], [1,1], [0,0], [1,1]]) - for slab in slab_list: - iS,iE, jS,jE, kS,kE = [item for bounds in slab for item in bounds] - if dim == 3: - DSFILEDA.extend([[iS,jS,kS], [1,1,1], [iE-iS, jE-jS, kE-kS], [1,1,1]]) - else: - DSFILEDA.extend([[iS,jS], [1,1], [iE-iS, jE-jS], [1,1]]) - DSMMRYDA = [[0] , [1] , [dn_da], [1]] - DSFILEDA = list([list(DSFILEDA)]) - DSGLOBDA = [list(data_shape)] - DSFORMDA = [[0]] - return DSMMRYDA + DSFILEDA + DSGLOBDA + DSFORMDA - -def create_flat_dataspace(distrib): - """ - Create the most basic dataspace (1d / flat) for a given - distribution. - """ - dn_da = distrib[1] - distrib[0] - DSMMRYDA = [[0 ], [1], [dn_da], [1]] - DSFILEDA = [[distrib[0]], [1], [dn_da], [1]] - DSGLOBDA = [[distrib[2]]] - DSFORMDA = [[0]] - return DSMMRYDA + DSFILEDA + DSGLOBDA + DSFORMDA - -def create_pe_dataspace(distrib): - """ - Create a dataspace from a flat distribution, of elements, - but adapted to "ParentElements" arrays ie (N,2) numpy arrays. - """ - dn_pe = distrib[1] - distrib[0] - DSMMRYPE = [[0 , 0], [1, 1], [dn_pe, 2], [1, 1]] - DSFILEPE = [[distrib[0], 0], [1, 1], [dn_pe, 2], [1, 1]] - DSGLOBPE = [[distrib[2], 2]] - DSFORMPE = [[1]] - return DSMMRYPE + DSFILEPE + DSGLOBPE + DSFORMPE - -def create_pointlist_dataspace(distrib, idx_dim=1): - """ - Create a dataspace from a flat distribution, but adapted to "fake 2d" arrays - ie (idx_dim, N) numpy arrays. - Mostly usefull for PointList arrays and DataArray of the related BCDataSets. - """ - dn_pl = distrib[1] - distrib[0] - DSMMRYPL = [[0,0 ], [1, 1], [idx_dim, dn_pl], [1, 1]] - DSFILEPL = [[0, distrib[0]], [1, 1], [idx_dim, dn_pl], [1, 1]] - DSGLOBPL = [[idx_dim, distrib[2]]] - DSFORMPL = [[0]] - return DSMMRYPL + DSFILEPL + DSGLOBPL + DSFORMPL - -def create_data_array_filter(distrib, data_shape=None): - """ - Create an hdf dataspace for the given distribution. The kind of - dataspace depends of the data_shape optional argument, representing - the size of the array for which the dataspace is created in each dimension: - - If data_shape is None or a single value, dataspace is 1d/flat - - Otherwise (which should correspond to true 2d array or 3d array), the - dataspace is create from combine method (flat in memory, block in file). - """ - if data_shape is None or len(data_shape) == 1: #Unstructured - hdf_data_space = create_flat_dataspace(distrib) - else: #Structured - hdf_data_space = create_combined_dataspace(data_shape, distrib) - - return hdf_data_space diff --git a/maia/io/hdf/test/test_cgns_elements.py b/maia/io/hdf/test/test_cgns_elements.py deleted file mode 100644 index ca394c26..00000000 --- a/maia/io/hdf/test/test_cgns_elements.py +++ /dev/null @@ -1,114 +0,0 @@ -import pytest -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.pytree.yaml import parse_yaml_cgns -from maia.io.hdf import cgns_elements - -def test_gen_elemts(): - yt = """ -Zone Zone_t [[27],[8],[0]]: - NGon Elements_t [22, 0]: - Hexa Elements_t [17, 0]: - SomeOtherNode OtherType_t: -""" - zone = parse_yaml_cgns.to_node(yt) - elmt_gen = cgns_elements.gen_elemts(zone) - assert hasattr(elmt_gen, '__next__') - assert PT.get_names(elmt_gen) == ['NGon', 'Hexa'] - -def test_create_zone_std_elements_filter(): - yt = """ -Hexa Elements_t [17, 0]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [2,7,10]: -Tri Elements_t [5, 0]: - ParentElements DataArray_t: - ParentElementsPosition DataArray_t: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [40,60,60]: -""" - elements = parse_yaml_cgns.to_nodes(yt) - hdf_filter = dict() - cgns_elements.create_zone_std_elements_filter(elements[0], "path/to/zone", hdf_filter) - cgns_elements.create_zone_std_elements_filter(elements[1], "path/to/zone", hdf_filter) - assert len(hdf_filter) == 4 - assert hdf_filter['path/to/zone/Hexa/ElementConnectivity'] == \ - [[0], [1], [(7-2)*8], [1], [2*8], [1], [(7-2)*8], [1], [10*8], [0]] - assert hdf_filter['path/to/zone/Tri/ElementConnectivity'] == \ - [[0], [1], [(60-40)*3], [1], [40*3], [1], [(60-40)*3], [1], [60*3], [0]] - assert hdf_filter['path/to/zone/Tri/ParentElements'] == \ - [[0,0], [1,1], [(60-40),2], [1,1], [40,0], [1,1], [(60-40),2], [1,1], [60,2], [1]] - -def test_load_element_connectivity_from_eso(): - yt = """ -NGon Elements_t [22, 0]: - ParentElements DataArray_t None: - ElementConnectivity#Size IndexArray_t [40]: - ElementStartOffset DataArray_t [8,12,16,20,24,28]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [2,7,10]: -""" - element = parse_yaml_cgns.to_node(yt) - hdf_filter = dict() - cgns_elements.load_element_connectivity_from_eso(element, 'pathtozone', hdf_filter) - assert hdf_filter['pathtozone/NGon/ElementConnectivity'] == \ - [[0], [1], [28-8], [1], [8], [1], [28-8], [1], [40], [0]] - element_connectivity_distri = PT.get_value(MT.getDistribution(element, 'ElementConnectivity')) - assert (element_connectivity_distri == [8,28,40]).all() - - -def test_create_zone_eso_elements_filter(): - yt = """ -NGon Elements_t [22, 0]: - ParentElements DataArray_t None: - ElementStartOffset DataArray_t None: - ElementConnectivity DataArray_t None: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [2,7,10]: -""" - element = parse_yaml_cgns.to_node(yt) - read_filter, write_filter = dict(), dict() - cgns_elements.create_zone_eso_elements_filter(element, 'pathtozone', read_filter, 'read') - cgns_elements.create_zone_eso_elements_filter(element, 'pathtozone', write_filter, 'write') - assert read_filter['pathtozone/NGon/ParentElements'] == \ - [[0, 0], [1, 1], [(7-2), 2], [1, 1], [2, 0], [1, 1], [(7-2), 2], [1, 1], [10, 2], [1]] - assert read_filter['pathtozone/NGon/ElementStartOffset'] == \ - [[0], [1], [(7-2)+1], [1], [2], [1], [(7-2)+1], [1], [10+1], [0]] - assert write_filter['pathtozone/NGon/ParentElements'] == read_filter['pathtozone/NGon/ParentElements'] - assert write_filter['pathtozone/NGon/ElementStartOffset'] == \ - [[0], [1], [(7-2)], [1], [2], [1], [(7-2)], [1], [10+1], [0]] - partial_func = read_filter['pathtozone/NGon/ElementConnectivity'] - assert partial_func.func is cgns_elements.load_element_connectivity_from_eso - assert partial_func.args == (element, 'pathtozone') - -def test_create_zone_elements_filter(): - yt = """ -Zone Zone_t: - NGon Elements_t [22, 0]: - ParentElements DataArray_t None: - ElementStartOffset DataArray_t None: - ElementConnectivity DataArray_t None: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [2,7,10]: - Tri Elements_t [5, 0]: - :CGNS#Distribution UserDefinedData_t: - Element DataArray_t [30,60,120]: -""" - zone = parse_yaml_cgns.to_node(yt) - hdf_filter = dict() - cgns_elements.create_zone_elements_filter(zone, 'zone', hdf_filter, 'read') - ngon = PT.get_node_from_name(zone, 'NGon') - tri = PT.get_node_from_name(zone, 'Tri') - ngon_filter, tri_filter = dict(), dict() - cgns_elements.create_zone_eso_elements_filter(ngon, 'zone', ngon_filter, 'read') - cgns_elements.create_zone_std_elements_filter(tri, 'zone', tri_filter) - for key,value in tri_filter.items(): - assert hdf_filter[key] == value - for key,value in ngon_filter.items(): - if 'ElementConnectivity' in key: - assert value.func == hdf_filter[key].func and value.args == hdf_filter[key].args - else: - assert hdf_filter[key] == value - assert len(hdf_filter) == (len(ngon_filter) + len(tri_filter)) - diff --git a/maia/io/hdf/test/test_cgns_subsets.py b/maia/io/hdf/test/test_cgns_subsets.py deleted file mode 100644 index 0edda627..00000000 --- a/maia/io/hdf/test/test_cgns_subsets.py +++ /dev/null @@ -1,230 +0,0 @@ -import pytest -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns -from maia.io.hdf import cgns_subsets - -def test_create_pl_filter(): - yt = """ - match1 GridConnectivity_t "ZoneA": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,10]: - PointListDonor IndexArray_t None: - PointListDonor#Size IndexArray_t [3,10]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [5,10,10]: - """ - hdf_filter = dict() - node = parse_yaml_cgns.to_node(yt) - - distri = PT.maia.getDistribution(node, 'Index')[1] - cgns_subsets._create_pl_filter(node, "path/to/node", "PointList", distri, hdf_filter) - cgns_subsets._create_pl_filter(node, "path/to/node", "PointListDonor", distri, hdf_filter) - - assert hdf_filter['path/to/node/PointList'] == [[0, 0], [1, 1], [1, 5], [1, 1], \ - [0, 5], [1, 1], [1, 5], [1, 1], [1, 10], [0]] - assert hdf_filter['path/to/node/PointListDonor'] == [[0, 0], [1, 1], [3, 5], [1, 1], \ - [0, 5], [1, 1], [3, 5], [1, 1], [3, 10], [0]] - - yt = """ - match1 GridConnectivity_t "ZoneA": - PointRange IndexRange_t [[1,3],[1,3],[1,1]]: - PointRangeDonor IndexRange_t [[1,3],[1,3],[3,3]]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [2,4,6]: - """ - hdf_filter = dict() - node = parse_yaml_cgns.to_node(yt) - distri = PT.maia.getDistribution(node, 'Index')[1] - cgns_subsets._create_pl_filter(node, "path/to/node", "PointList", distri, hdf_filter) - assert len(hdf_filter) == 0 - -def test_create_zone_bc_filter(): - #Don't test the value of value of dataspace, this is done by test_hdf_dataspace - yt = """ -Base CGNSBase_t [3,3]: - Zone Zone_t [[27],[8],[0]]: - ZBC ZoneBC_t: - bc_only BC_t "wall": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,10]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [5,10,10]: - bc_with_ds BC_t "wall": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,50]: - BCDataSet BCDataSet_t: - BCData BCData_t: - array1 DataArray_t None: - array2 DataArray_t None: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [20,50,50]: - bc_with_subds BC_t "wall": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,50]: - BCDataSet BCDataSet_t: - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,30]: - BCData BCData_t: - array1 DataArray_t None: - array2 DataArray_t None: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [20,30,30]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [20,50,50]: -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - hdf_filter = dict() - cgns_subsets.create_zone_bc_filter(PT.get_all_Zone_t(size_tree)[0], "Base/Zone", hdf_filter) - assert len(hdf_filter.keys()) == 8 - prefix = 'Base/Zone/ZBC/' - assert prefix+'bc_only/PointList' in hdf_filter - assert prefix+'bc_with_ds/PointList' in hdf_filter - assert prefix+'bc_with_ds/BCDataSet/BCData/array1' in hdf_filter - assert prefix+'bc_with_ds/BCDataSet/BCData/array2' in hdf_filter - assert prefix+'bc_with_subds/PointList' in hdf_filter - assert prefix+'bc_with_subds/BCDataSet/PointList' in hdf_filter - assert prefix+'bc_with_subds/BCDataSet/BCData/array2' in hdf_filter - assert prefix+'bc_with_subds/BCDataSet/BCData/array1' in hdf_filter - assert hdf_filter[prefix+'bc_with_subds/BCDataSet/PointList'] !=\ - hdf_filter[prefix+'bc_with_subds/PointList'] - -def test_create_zone_grid_connectivity_filter(): - #Don't test the value of value of dataspace, this is done by test_hdf_dataspace - yt = """ -Base CGNSBase_t [3,3]: - ZoneU Zone_t [[27],[8],[0]]: - ZGC ZoneGridConnectivity_t: - match1 GridConnectivity_t "ZoneA": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,10]: - PointListDonor IndexArray_t None: - PointListDonor#Size IndexArray_t [1,10]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [5,10,10]: - match2 GridConnectivity_t "Base1/ZoneC": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,50]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [20,50,50]: - ZoneS Zone_t None: - ZGC ZoneGridConnectivity_t: - match3 GridConnectivity1to1_t "ZoneA": - PointRange IndexRange_t [[1,3],[1,3],[1,1]]: - PointRangeDonor IndexRange_t [[1,3],[1,3],[3,3]]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [5,9,9]: -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - hdf_filter = dict() - zoneU = PT.get_node_from_name(size_tree, 'ZoneU') - zoneS = PT.get_node_from_name(size_tree, 'ZoneS') - cgns_subsets.create_zone_grid_connectivity_filter(zoneU, "Base/ZoneU", hdf_filter) - cgns_subsets.create_zone_grid_connectivity_filter(zoneS, "Base/ZoneS", hdf_filter) - assert len(hdf_filter.keys()) == 3 - assert 'Base/ZoneU/ZGC/match1/PointList' in hdf_filter - assert 'Base/ZoneU/ZGC/match1/PointListDonor' in hdf_filter - assert 'Base/ZoneU/ZGC/match2/PointList' in hdf_filter - assert 'Base/ZoneS/ZGC/match3/PointRange' not in hdf_filter #Point range are ignored - -def test_create_flow_solution_filter(): - #Don't test the value of value of dataspace, this is done by test_hdf_dataspace - yt = """ -Base CGNSBase_t [3,3]: - Zone Zone_t [[27],[8],[0]]: - FSall FlowSolution_t: - GridLocation GridLocation_t "Vertex": - array1 DataArray_t None: - array2 DataArray_t None: - FSpartial FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,4]: - array3 DataArray_t None: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [0,4,4]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t [12,27,27]: - Cell DataArray_t [0,8,8]: -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - hdf_filter = dict() - cgns_subsets.create_flow_solution_filter(PT.get_all_Zone_t(size_tree)[0], "Base/Zone", hdf_filter) - assert len(hdf_filter.keys()) == 4 - assert 'Base/Zone/FSall/array1' in hdf_filter - assert 'Base/Zone/FSall/array2' in hdf_filter - assert 'Base/Zone/FSpartial/array3' in hdf_filter - assert 'Base/Zone/FSpartial/PointList' in hdf_filter - - yt = """ -Base CGNSBase_t [3,3]: - Zone Zone_t [[27],[8],[0]]: - wrongFS FlowSolution_t: - GridLocation GridLocation_t "FaceCenter": - array1 DataArray_t None: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t [12,27,27]: - Cell DataArray_t [0,8,8]: -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - hdf_filter = dict() - with pytest.raises(RuntimeError): - cgns_subsets.create_flow_solution_filter(PT.get_all_Zone_t(size_tree)[0], "Base/Zone", hdf_filter) - -def test_create_zone_subregion_filter(): - #Don't test the value of value of dataspace, this is done by test_hdf_dataspace - yt = """ -Base CGNSBase_t [3,3]: - Zone Zone_t [[27],[8],[0]]: - defined_ZSR ZoneSubRegion_t: - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,10]: - array1 DataArray_t None: - array2 DataArray_t None: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [5,10,10]: - linked_ZSR ZoneSubRegion_t: - BCRegionName Descriptor_t "bc": - array1 DataArray_t None: - array2 DataArray_t None: - linked_ZSR2 ZoneSubRegion_t: - GridConnectivityRegionName Descriptor_t "bc": - array1 DataArray_t None: - array2 DataArray_t None: - ZoneBC ZoneBC_t: - bc BC_t "farfield": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,10]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [5,10,10]: - ZoneGC ZoneGridConnectivity_t: - bc GridConnectivity_t: - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,40]: - PointListDonor IndexArray_t None: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [20,40,40]: -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - hdf_filter = dict() - cgns_subsets.create_zone_subregion_filter(PT.get_all_Zone_t(size_tree)[0], "Base/Zone", hdf_filter) - assert len(hdf_filter.keys()) == 7 - assert 'Base/Zone/defined_ZSR/PointList' in hdf_filter - assert 'Base/Zone/defined_ZSR/array1' in hdf_filter - assert 'Base/Zone/defined_ZSR/array2' in hdf_filter - assert 'Base/Zone/linked_ZSR/array1' in hdf_filter - assert 'Base/Zone/linked_ZSR/array2' in hdf_filter - assert 'Base/Zone/linked_ZSR2/array2' in hdf_filter - assert 'Base/Zone/linked_ZSR2/array2' in hdf_filter - - yt = """ -Base CGNSBase_t [3,3]: - Zone Zone_t [[27],[8],[0]]: - unlinked_ZSR ZoneSubRegion_t: - array1 DataArray_t None: - array2 DataArray_t None: -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - hdf_filter = dict() - with pytest.raises(RuntimeError): - cgns_subsets.create_zone_subregion_filter(PT.get_all_Zone_t(size_tree)[0], "Base/Zone", hdf_filter) diff --git a/maia/io/hdf/test/test_hdf_cgns.py b/maia/io/hdf/test/test_hdf_cgns.py deleted file mode 100644 index 5280f78c..00000000 --- a/maia/io/hdf/test/test_hdf_cgns.py +++ /dev/null @@ -1,307 +0,0 @@ -import pytest -import pytest_parallel - -import numpy as np -import shutil -import subprocess -from pathlib import Path -from h5py import h5f, h5g - -import maia.pytree as PT - -from maia.pytree.yaml import parse_yaml_cgns -from maia.utils.test_utils import sample_mesh_dir - - -from maia.io.hdf import _hdf_cgns as HCG - -sample_tree = """ - Base CGNSBase_t [2,2]: - ZoneU Zone_t [[6, 0, 0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [1., 2., 3., 4., 5., 6.]: - CoordinateY DataArray_t R8 [-1., -2., -3., -4., -5., -6.]: - ZoneS Zone_t [[2, 1, 0], [2, 1, 0]]: - ZoneType ZoneType_t 'Structured': - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [[1., 2.], [3., 4.]]: - CoordinateY DataArray_t R8 [[-1., -1.], [0., 0.]]: - """ - -@pytest.fixture -def ref_hdf_file(): - return str(sample_mesh_dir / 'only_coords.hdf') - -@pytest.fixture -def tmp_hdf_file(tmp_path, ref_hdf_file): - """ Copy the ref hdf in a temporary dir for tests modifying it """ - shutil.copy(ref_hdf_file, tmp_path) - return str(tmp_path / Path('only_coords.hdf')) - -def get_subprocess_stdout(cmd): - """ Run a command line using subprocess.run and return the stdout as a list of lines """ - output = subprocess.run(cmd.split(), capture_output=True) - return [s.strip() for s in output.stdout.decode().split('\n')] - -def check_h5ls_output(output, key, expected_value): - # Some hdf version include a line break in h5ls output - idx = output.index(key) #Will raise if not in list - if output[idx+2] == "Data:": # Line break after data - assert output[idx+3] == expected_value - else: # Everything on the same line - assert output[idx+2].split(':')[1].strip() == expected_value - -class Test_AttributeRW: - attr_rw = HCG.AttributeRW() - assert attr_rw.buff_S33.size == 1 and attr_rw.buff_S33.dtype == 'S33' - assert attr_rw.buff_flag1.size == 1 and attr_rw.buff_flag1.dtype == np.int32 - - def test_read(self, ref_hdf_file): - # Read - fid = h5f.open(bytes(ref_hdf_file, 'utf-8'), h5f.ACC_RDONLY) - gid = HCG.open_from_path(fid, 'Base/ZoneU/GridCoordinates') - assert self.attr_rw.read_str_33(gid, b'name') == 'GridCoordinates' - assert self.attr_rw.read_bytes_3(gid, b'type') == b'MT' - - def test_write(self, tmp_hdf_file): - fid = h5f.open(bytes(tmp_hdf_file, 'utf-8'), h5f.ACC_RDWR) - gid = HCG.open_from_path(fid, 'Base/ZoneU') - gid = h5g.create(gid, 'ZoneTypeNew'.encode()) - - self.attr_rw.write_str_33(gid, b'some_attribute', 'AttrValue') - self.attr_rw.write_flag(gid) - gid.close() - fid.close() - - out = get_subprocess_stdout(f"h5ls -gvd {tmp_hdf_file}/Base/ZoneU/ZoneTypeNew") - check_h5ls_output(out, "Attribute: some_attribute scalar", '"AttrValue"') - check_h5ls_output(out, "Attribute: flags {1}", '1') - -def test_add_root_attribute(tmp_hdf_file): - fid = h5f.open(bytes(tmp_hdf_file, 'utf-8'), h5f.ACC_RDWR) - gid = HCG.open_from_path(fid, 'Base/ZoneU') - gid = h5g.create(gid, 'FakeRoot'.encode()) - HCG.add_root_attributes(gid) - gid.close() - fid.close() - - out = get_subprocess_stdout(f"h5ls -gvd {tmp_hdf_file}/Base/ZoneU/FakeRoot") - check_h5ls_output(out, "Attribute: label scalar", '"Root Node of HDF5 File"') - - cmd = ["h5ls", "-gvd", f"{tmp_hdf_file}/Base/ZoneU/FakeRoot/ hdf5version"] - assert subprocess.run(cmd, capture_output=True).returncode == 0 - cmd = ["h5ls", "-gvd", f"{tmp_hdf_file}/Base/ZoneU/FakeRoot/ format"] - assert subprocess.run(cmd, capture_output=True).returncode == 0 - -def test_is_combinated(): - assert HCG.is_combinated([[0], [1], [100], [1], [0], [1], [100], [1], [100], [0]]) == False - assert HCG.is_combinated([[0,0], [1,1], [1,10], [1,1], [0,0], [1,1], [1,10], [1,1], [1,100], [0]]) == False - assert HCG.is_combinated([[0], [1], [10], [1], [[0, 0, 0], [1, 1, 1], [10, 1, 1], [1, 1, 1]], [10, 2, 5], [0]]) == True - -def test_group_by(): - for i, elts in enumerate(HCG.group_by(['a','b','c', 'd','e','f'], 3)): - if i == 0: - assert elts == ('a', 'b', 'c') - if i == 1: - assert elts == ('d', 'e', 'f') - -def test_open_from_path(ref_hdf_file): - fid = h5f.open(bytes(ref_hdf_file, 'utf-8'), h5f.ACC_RDONLY) - # If this not raise, open is OK - gid = HCG.open_from_path(fid, 'Base/ZoneS/ZoneType') - gid = HCG.open_from_path(fid, 'Base') - -def test_load_data(ref_hdf_file): - fid = h5f.open(bytes(ref_hdf_file, 'utf-8'), h5f.ACC_RDONLY) - gid = HCG.open_from_path(fid, 'Base/ZoneU') - data = HCG.load_data(gid) - assert np.array_equal(data, [[6,0,0]]) and data.dtype == np.int32 and data.flags.f_contiguous - - gid = HCG.open_from_path(fid, 'Base/ZoneU/GridCoordinates/CoordinateX') - data = HCG.load_data(gid) - assert np.array_equal(data, [1,2,3,4,5,6]) and data.dtype == np.float64 - - gid = HCG.open_from_path(fid, 'Base/ZoneS/GridCoordinates/CoordinateX') - data = HCG.load_data(gid) - assert np.array_equal(data, [[1,2], [3,4]]) and data.dtype == np.float64 and np.isfortran(data) - -def test_write_data(tmp_hdf_file): - fid = h5f.open(bytes(tmp_hdf_file, 'utf-8'), h5f.ACC_RDWR) - gid = HCG.open_from_path(fid, 'Base/ZoneU/GridCoordinates') - gid = h5g.create(gid, 'CoordinateZ'.encode()) - HCG.write_data(gid, np.array([0,0,0, 1,1,1], np.float64)) - gid.close() - fid.close() - - out = get_subprocess_stdout(f"h5ls -vd {tmp_hdf_file}/Base/ZoneU/GridCoordinates/CoordinateZ") - idx = out.index("Type: native double") #Will raise if not in list - idx = out.index("Data:") #Will raise if not in list - if out[idx+1].startswith('('): - out[idx+1] = out[idx+1][4:] #Some hdf version include (0) before data : remote it - assert out[idx+1] == '0, 0, 0, 1, 1, 1' - -def test_write_link(tmp_hdf_file): - fid = h5f.open(bytes(tmp_hdf_file, 'utf-8'), h5f.ACC_RDWR) - gid = HCG.open_from_path(fid, 'Base/ZoneU/GridCoordinates') - HCG.write_link(gid, 'CoordinateZ', 'this/hdf/file.hdf', 'this/node') - gid.close() - fid.close() - out = get_subprocess_stdout(f"h5ls -vdg {tmp_hdf_file}/Base/ZoneU/GridCoordinates/CoordinateZ") - check_h5ls_output(out, "Attribute: type scalar", '"LK"') - out = get_subprocess_stdout(f"h5ls -vd {tmp_hdf_file}/Base/ZoneU/GridCoordinates/CoordinateZ") - idx = out.index("\\ file Dataset {18/18}") - idx = out.index("\\ path Dataset {10/10}") - for l in out: - if "\\ link" in l: - assert "External Link {this/hdf/file.hdf//this/node}" in l - break - else: - assert False - -def test_read_links(tmp_hdf_file): - assert HCG.load_tree_links(tmp_hdf_file) == [] - fid = h5f.open(bytes(tmp_hdf_file, 'utf-8'), h5f.ACC_RDWR) - gid = HCG.open_from_path(fid, 'Base/ZoneU/GridCoordinates') - HCG.write_link(gid, 'CoordinateZ', 'this/hdf/file.hdf', 'this/node') - gid.close() - gid = HCG.open_from_path(fid, 'Base/ZoneS/GridCoordinates') - HCG.write_link(gid, 'CoordinateZ', 'this/hdf/file.hdf', 'this/other_node') - gid.close() - fid.close() - links = HCG.load_tree_links(tmp_hdf_file) - assert links[0] == ['.', 'this/hdf/file.hdf', 'this/node', 'Base/ZoneU/GridCoordinates/CoordinateZ'] - assert links[1] == ['.', 'this/hdf/file.hdf', 'this/other_node', 'Base/ZoneS/GridCoordinates/CoordinateZ'] - - -def test_load_data_partial(ref_hdf_file): - fid = h5f.open(bytes(ref_hdf_file, 'utf-8'), h5f.ACC_RDONLY) - - gid = HCG.open_from_path(fid, 'Base/ZoneU/GridCoordinates/CoordinateX') - data = HCG.load_data_partial(gid, [[0], [1], [3], [1], [3], [1], [3], [1], [6], [1]]) - assert np.array_equal(data, [4,5,6]) and data.dtype == np.float64 - - gid = HCG.open_from_path(fid, 'Base/ZoneS/GridCoordinates/CoordinateX') - # Simple - data = HCG.load_data_partial(gid, [[0,0], [1,1], [1,2], [1,1], - [1,0], [1,1], [1,2], [1,1], [2,2], [0]]) - assert np.array_equal(data, [[3,4]]) and data.dtype == np.float64 and data.flags.f_contiguous - - # Multiple - data = HCG.load_data_partial(gid, [[0], [1], [3], [1], - [[1,0], [1,1], [1,2], [1,1], [0,0],[1,1],[1,1],[1,1]], [2,2], [0]]) - assert np.array_equal(data, [1,3,4]) and data.dtype == np.float64 - -@pytest.mark.parametrize('combinated', [False, True]) -def test_write_data_partial(tmp_hdf_file, combinated): - fid = h5f.open(bytes(tmp_hdf_file, 'utf-8'), h5f.ACC_RDWR) - gid = HCG.open_from_path(fid, 'Base/ZoneU/GridCoordinates') - gid = h5g.create(gid, 'CoordinateZ'.encode()) - if combinated: - filter = [[0], [1], [4], [1], [[0], [1], [3], [1], [5], [1], [1], [1]], [6], [1]] - array = np.array([-10,-20,-30,-100], np.float64) - indexes = np.array([0,1,2,5]) - else: - filter = [[0], [1], [2], [1], [3], [1], [2], [1], [6], [1]] - array = np.array([-1,-2], np.float64) - indexes = np.array([3,4]) - HCG.write_data_partial(gid, array, filter) - gid.close() - fid.close() - - out = get_subprocess_stdout(f"h5ls -vd {tmp_hdf_file}/Base/ZoneU/GridCoordinates/CoordinateZ") - idx = out.index("Data:") #Will raise if not in list - if out[idx+1].startswith('('): - out[idx+1] = out[idx+1][4:] #Some hdf version include (0) before data : remote it - written_array = np.array([float(x) for x in out[idx+1].split(',')]) - assert np.allclose(written_array[indexes], array) - -@pytest.mark.parametrize('partial', [True,False]) -def test_load_node_partial(partial, ref_hdf_file): - fid = h5f.open(bytes(ref_hdf_file, 'utf-8'), h5f.ACC_RDONLY) - gid = HCG.open_from_path(fid, 'Base/ZoneU/GridCoordinates') - - parent = ['ZoneU', None, [], 'Zone_t'] - ancestors_stack = (['Base', 'ZoneU'], ['CGNSBase_t', 'Zone_t']) - - if partial: - # Load only one array - HCG._load_node_partial(gid, parent, lambda N,L : N[-1] != 'CoordinateY', ancestors_stack) - yt = """ - ZoneU Zone_t: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [1,2,3,4,5,6]: - CoordinateY DataArray_t: - CoordinateY#Size DataArray_t I8 [6]: - """ - else: - HCG._load_node_partial(gid, parent, lambda N,L : True, ancestors_stack) - yt = """ - ZoneU Zone_t: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [1,2,3,4,5,6]: - CoordinateY DataArray_t R8 [-1,-2,-3,-4,-5,-6]: - """ - - assert PT.is_same_tree(parent, parse_yaml_cgns.to_node(yt)) - -def test_write_node_partial(tmp_hdf_file): - tree = parse_yaml_cgns.to_cgns_tree(sample_tree) - node = PT.get_node_from_path(tree, 'Base/ZoneU/GridCoordinates') - ancestors_stack = (['Base', 'ZoneU', 'GridCoordinates'], ['CGNSBase_t', 'Zone_t', 'GridCoordinates_t']) - - fid = h5f.open(bytes(tmp_hdf_file, 'utf-8'), h5f.ACC_RDWR) - gid = HCG.open_from_path(fid, 'Base/ZoneU') - - gid.unlink(b'GridCoordinates') # Remove before writting - HCG._write_node_partial(gid, node, lambda N,L: N[-1] != 'CoordinateY', ancestors_stack) - - gid.close() - fid.close() - - # Check node - out = get_subprocess_stdout(f"h5ls -vgd {tmp_hdf_file}/Base/ZoneU/GridCoordinates") - check_h5ls_output(out, "Attribute: type scalar", '"MT"') - check_h5ls_output(out, "Attribute: label scalar", '"GridCoordinates_t"') - - #Check children - for coord in ['X', 'Y']: - out = get_subprocess_stdout(f"h5ls -vgd {tmp_hdf_file}/Base/ZoneU/GridCoordinates/Coordinate{coord}") - check_h5ls_output(out, "Attribute: type scalar", '"R8"') - - out = get_subprocess_stdout(f"h5ls -r {tmp_hdf_file}/Base/ZoneU/GridCoordinates/Coordinate{coord}") - if coord == 'X': - assert 'data' in out[0] - else: - assert out == [''] - -@pytest.mark.parametrize('partial', [True,False]) -def test_load_tree_partial(partial, ref_hdf_file): - if partial: - tree = HCG.load_tree_partial(ref_hdf_file, lambda N,L : N[-1] != 'CoordinateY') - yt = """ - Base CGNSBase_t [2,2]: - ZoneU Zone_t [[6, 0, 0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [1., 2., 3., 4., 5., 6.]: - CoordinateY DataArray_t: - CoordinateY#Size DataArray_t I8 [6]: - ZoneS Zone_t [[2, 1, 0], [2, 1, 0]]: - ZoneType ZoneType_t 'Structured': - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [[1., 2.], [3., 4.]]: - CoordinateY DataArray_t: - CoordinateY#Size DataArray_t I8 [2,2]: - """ - else: - tree = HCG.load_tree_partial(ref_hdf_file, lambda N,L : True) - yt = sample_tree - assert PT.is_same_tree(tree, parse_yaml_cgns.to_cgns_tree(yt)) - -def test_write_tree_partial(tmp_path, ref_hdf_file): - tree = parse_yaml_cgns.to_cgns_tree(sample_tree) - outfile = str(tmp_path / Path('only_coords.hdf')) - HCG.write_tree_partial(tree, outfile, lambda N,L : True) - cmd = ["h5diff", f"{ref_hdf_file}", f"{outfile}", "Base"] #hdf5version dataset can vary - assert subprocess.run(cmd).returncode == 0 diff --git a/maia/io/hdf/test/test_hdf_dataspace.py b/maia/io/hdf/test/test_hdf_dataspace.py deleted file mode 100644 index 42e86450..00000000 --- a/maia/io/hdf/test/test_hdf_dataspace.py +++ /dev/null @@ -1,56 +0,0 @@ -from maia.io.hdf import hdf_dataspace - -def test_create_flat_dataspace(): - assert hdf_dataspace.create_flat_dataspace([0, 10, 100]) == \ - [[0], [1], [10], [1], [0], [1], [10], [1], [100], [0]] - assert hdf_dataspace.create_flat_dataspace([50, 60, 100]) == \ - [[0], [1], [60-50], [1], [50], [1], [60-50], [1], [100], [0]] - assert hdf_dataspace.create_flat_dataspace([0, 100, 100]) == \ - [[0], [1], [100], [1], [0], [1], [100], [1], [100], [0]] - assert hdf_dataspace.create_flat_dataspace([50, 50, 100]) == \ - [[0], [1], [0], [1], [50], [1], [0], [1], [100], [0]] - assert hdf_dataspace.create_flat_dataspace([0, 0, 0]) == \ - [[0], [1], [0], [1], [0], [1], [0], [1], [0], [0]] - -def test_create_pointlist_dataspace(): - assert hdf_dataspace.create_pointlist_dataspace([0, 10, 100]) == \ - [[0,0], [1,1], [1,10], [1,1], [0,0], [1,1], [1,10], [1,1], [1,100], [0]] - assert hdf_dataspace.create_pointlist_dataspace([0, 10, 100],3) == \ - [[0,0], [1,1], [3,10], [1,1], [0,0], [1,1], [3,10], [1,1], [3,100], [0]] - assert hdf_dataspace.create_pointlist_dataspace([0, 100, 100]) == \ - [[0,0], [1,1], [1,100], [1,1], [0,0], [1,1], [1,100], [1,1], [1,100], [0]] - assert hdf_dataspace.create_pointlist_dataspace([50, 50, 100]) == \ - [[0,0], [1,1], [1,0], [1,1], [0,50], [1,1], [1,0], [1,1], [1,100], [0]] - assert hdf_dataspace.create_pointlist_dataspace([0, 0, 0]) == \ - [[0,0], [1,1], [1,0], [1,1], [0,0], [1,1], [1,0], [1,1], [1,0], [0]] - -def test_create_pe_dataspace(): - assert hdf_dataspace.create_pe_dataspace([0, 10, 100]) == \ - [[0,0], [1,1], [10,2], [1,1], [0,0], [1,1], [10,2], [1,1], [100,2], [1]] - assert hdf_dataspace.create_pe_dataspace([0, 100, 100]) == \ - [[0,0], [1,1], [100,2], [1,1], [0,0], [1,1], [100,2], [1,1], [100,2], [1]] - assert hdf_dataspace.create_pe_dataspace([0, 0, 0]) == \ - [[0,0], [1,1], [0,2], [1,1], [0,0], [1,1], [0,2], [1,1], [0,2], [1]] - -def test_create_3D_combined_dataspace(): - assert hdf_dataspace.create_combined_dataspace([10,2,5], [0,10,100]) == \ - [[0], [1], [10], [1], [[0, 0, 0], [1, 1, 1], [10, 1, 1], [1, 1, 1]], [10, 2, 5], [0]] - assert hdf_dataspace.create_combined_dataspace([2,5,10], [0,10,100]) == \ - [[0], [1], [10], [1], [[0, 0, 0], [1, 1, 1], [2, 5, 1], [1, 1, 1]], [2, 5, 10], [0]] - assert hdf_dataspace.create_combined_dataspace([2,5,10], [50,50,100]) == \ - [[0], [1], [0], [1], [[0,0,0], [1,1,1], [0,0,0], [1,1,1]], [2, 5, 10], [0]] - -def test_create_2D_combined_dataspace(): - assert hdf_dataspace.create_combined_dataspace([10,2], [0,10,20]) == \ - [[0], [1], [10], [1], [[0, 0], [1, 1], [10, 1], [1, 1]], [10, 2], [0]] - assert hdf_dataspace.create_combined_dataspace([2,5], [0,3,10]) == \ - [[0], [1], [3], [1], [[0, 0], [1, 1], [2, 1], [1, 1], [0, 1], [1, 1], [1, 1], [1, 1]], [2, 5], [0]] - assert hdf_dataspace.create_combined_dataspace([2,5], [5,5,10]) == \ - [[0], [1], [0], [1], [[0,0], [1,1], [0,0], [1,1]], [2, 5], [0]] - -def test_create_data_array_filter(): - assert hdf_dataspace.create_data_array_filter([0,10,100]) == \ - hdf_dataspace.create_flat_dataspace([0,10,100]) - assert hdf_dataspace.create_data_array_filter([0,10,100], [5,4,5]) == \ - hdf_dataspace.create_combined_dataspace([5,4,5], [0,10,100]) - diff --git a/maia/io/hdf/test/test_hdf_filter_utils.py b/maia/io/hdf/test/test_hdf_filter_utils.py deleted file mode 100644 index 3d1cebab..00000000 --- a/maia/io/hdf/test/test_hdf_filter_utils.py +++ /dev/null @@ -1,13 +0,0 @@ -import maia.pytree as PT - -from maia.io.hdf import utils - -def test_apply_dataspace_to_arrays(): - hdf_filter = dict() - node = PT.new_node('Parent', 'UserDefinedData_t') - for child in ['child1', 'child2', 'child3']: - PT.new_DataArray(child, None, parent=node) - utils.apply_dataspace_to_arrays(node, "path/to/Parent", "my_data_space", hdf_filter) - for child in ['child1', 'child2', 'child3']: - assert hdf_filter['path/to/Parent/' + child] == "my_data_space" - diff --git a/maia/io/hdf/tree.py b/maia/io/hdf/tree.py deleted file mode 100644 index 11fc8a09..00000000 --- a/maia/io/hdf/tree.py +++ /dev/null @@ -1,48 +0,0 @@ -import fnmatch - -import maia.pytree as PT -import maia.pytree.maia as MT - -from .hdf_dataspace import create_data_array_filter -from .cgns_elements import create_zone_elements_filter -from .cgns_subsets import create_zone_subregion_filter,\ - create_flow_solution_filter,\ - create_zone_bc_filter,\ - create_zone_grid_connectivity_filter -from . import utils - -def create_zone_filter(zone, zone_path, hdf_filter, mode): - """ - Fill up the hdf filter for the following elements of the zone: - Coordinates, Elements (NGon / NFace, Standards), FlowSolution - (vertex & cells only), ZoneSubRegion, ZoneBC (including BCDataSet) - and ZoneGridConnectivity. - - The bounds of the filter are determined by the :CGNS#Distribution - node and, for the structured zones, by the size of the blocks. - """ - # Coords - distrib_vtx = PT.get_value(MT.getDistribution(zone, 'Vertex')) - all_vtx_dataspace = create_data_array_filter(distrib_vtx, zone[1][:,0]) - for grid_c in PT.iter_children_from_label(zone, 'GridCoordinates_t'): - grid_coord_path = zone_path + "/" + PT.get_name(grid_c) - utils.apply_dataspace_to_arrays(grid_c, grid_coord_path, all_vtx_dataspace, hdf_filter) - - create_zone_elements_filter(zone, zone_path, hdf_filter, mode) - - create_zone_bc_filter(zone, zone_path, hdf_filter) - create_zone_grid_connectivity_filter(zone, zone_path, hdf_filter) - create_flow_solution_filter(zone, zone_path, hdf_filter) - create_zone_subregion_filter(zone, zone_path, hdf_filter) - - -def create_tree_hdf_filter(dist_tree, mode='read'): - """ - On a besoin du write pour gérer le ElementStartIndex - It can be replace by a if None in tree to see if read/write ? - """ - hdf_filter = dict() - for base, zone in PT.iter_nodes_from_predicates(dist_tree, 'CGNSBase_t/Zone_t', ancestors=True): - zone_path = PT.get_name(base)+"/"+PT.get_name(zone) - create_zone_filter(zone, zone_path, hdf_filter, mode) - return hdf_filter diff --git a/maia/io/hdf/utils.py b/maia/io/hdf/utils.py deleted file mode 100644 index c4ce548c..00000000 --- a/maia/io/hdf/utils.py +++ /dev/null @@ -1,11 +0,0 @@ -import maia.pytree as PT - -def apply_dataspace_to_arrays(node, node_path, data_space, hdf_filter): - """ - Fill the hdf_filter with the specified data_space for all the DataArray_t nodes - below the parent node node - """ - for data_array in PT.iter_children_from_label(node, 'DataArray_t'): - path = node_path+"/"+data_array[0] - hdf_filter[path] = data_space - diff --git a/maia/io/meshb_converter.py b/maia/io/meshb_converter.py deleted file mode 100644 index 016c83b8..00000000 --- a/maia/io/meshb_converter.py +++ /dev/null @@ -1,307 +0,0 @@ -import time -import mpi4py.MPI as MPI - -import maia -import maia.pytree as PT -import maia.pytree.maia as MT -import maia.utils.logging as mlog - -from maia import npy_pdm_gnum_dtype as pdm_gnum_dtype -from maia.utils import np_utils, par_utils -from maia.factory.dcube_generator import _dmesh_nodal_to_cgns_zone - -import numpy as np - -import Pypdm.Pypdm as PDM - - -def get_tree_info(dist_tree, container_names): - """ - Get tree informations such as bc_names and interpolated containers. - """ - - zones = PT.get_all_Zone_t(dist_tree) - assert len(zones) == 1 - zone_n = zones[0] - - # > Get BCs infos - bc_names = dict() - for entity_name in ["EdgeCenter", "FaceCenter", "CellCenter"]: - is_entity_bc = lambda n :PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n)==entity_name - entity_bcs = PT.get_children_from_predicates(zone_n, ['ZoneBC_t', is_entity_bc]) - bc_names[entity_name] = [PT.get_name(bc_n) for bc_n in entity_bcs] - - # > Container field names - field_names = dict() - for container_name in container_names: - container = PT.get_node_from_name(zone_n, container_name) - assert PT.Subset.GridLocation(container) == 'Vertex' - field_names[container_name] = [PT.get_name(n) for n in PT.iter_children_from_label(container, 'DataArray_t')] - - return {"bc_names" : bc_names, - "field_names" : field_names} - - -def dmesh_nodal_to_cgns(dmesh_nodal, comm, tree_info, out_files): - """ - Convert a dmesh_nodal mesh to CGNS format, according to initial dist_tree informations - contained in ``tree_info``. - """ - - # > Generate dist_tree - g_dims = dmesh_nodal.dmesh_nodal_get_g_dims() - cell_dim = 3 if g_dims["n_cell_abs"]>0 else 2 - dist_tree = PT.new_CGNSTree() - dist_base = PT.new_CGNSBase(cell_dim=cell_dim, phy_dim=3, parent=dist_tree) - dist_zone = _dmesh_nodal_to_cgns_zone(dmesh_nodal, comm) - PT.add_child(dist_base, dist_zone) - - - # > BCs - vtx_groups = dmesh_nodal.dmesh_nodal_get_group(PDM._PDM_GEOMETRY_KIND_CORNER) - edge_groups = dmesh_nodal.dmesh_nodal_get_group(PDM._PDM_GEOMETRY_KIND_RIDGE) - face_groups = dmesh_nodal.dmesh_nodal_get_group(PDM._PDM_GEOMETRY_KIND_SURFACIC) - cell_groups = dmesh_nodal.dmesh_nodal_get_group(PDM._PDM_GEOMETRY_KIND_VOLUMIC) - - vtx_data = dmesh_nodal.dmesh_nodal_get_vtx(comm) - vtx_tag = vtx_data['np_vtx_tag'] - - - bc_names = tree_info['bc_names'] - def groups_to_bcs(elt_groups, zone_bc, location, shift_bc, comm): - elt_group_idx = elt_groups['dgroup_elmt_idx'] - elt_group = elt_groups['dgroup_elmt'] + shift_bc - n_elt_group = elt_group_idx.shape[0] - 1 - - n_bc_init = len(bc_names[location]) if location in bc_names else 0 - n_new_bc = n_elt_group - n_bc_init - assert n_new_bc in [0,1], f"Unknow tags in meshb file ({location})" - - for i_group in range(n_elt_group): - if bc_names[location] or edge_groups is not None: - if i_group < n_new_bc: - name_bc = {"Vertex":"vtx", "EdgeCenter":"edge", "FaceCenter":"face", "CellCenter":"cell"} - bc_name = f"feflo_{name_bc[location]}_bc_{i_group}" - # continue # For now, skip BC detected in meshb but not provided in BC names - else: - bc_name = bc_names[location][i_group-n_new_bc] - - bc_n = PT.new_BC(bc_name, type='Null', loc=location, parent=zone_bc) - start, end = elt_group_idx[i_group], elt_group_idx[i_group+1] - dn_elt_bnd = end - start - PT.new_IndexArray(value=elt_group[start:end].reshape((1,-1), order='F'), parent=bc_n) - - bc_distrib = par_utils.gather_and_shift(dn_elt_bnd, comm, pdm_gnum_dtype) - MT.newDistribution({'Index' : par_utils.dn_to_distribution(dn_elt_bnd, comm)}, parent=bc_n) - - - zone_bc = PT.new_ZoneBC(parent=dist_zone) - range_per_dim = PT.Zone.get_elt_range_per_dim(dist_zone) - - if cell_groups is not None: - groups_to_bcs(cell_groups, zone_bc, "CellCenter", 0, comm) - if face_groups is not None: - groups_to_bcs(face_groups, zone_bc, "FaceCenter", range_per_dim[3][1], comm) - if edge_groups is not None: - groups_to_bcs(edge_groups, zone_bc, "EdgeCenter", range_per_dim[2][1], comm) - if vtx_groups is not None: - groups_to_bcs(vtx_groups, zone_bc, "Vertex", range_per_dim[1][1], comm) - - # > Add FlowSolution - n_vtx = PT.Zone.n_vtx(dist_zone) - distrib_vtx = PT.get_value(MT.getDistribution(dist_zone, "Vertex")) - - field_names = tree_info['field_names'] - n_itp_flds = sum([len(fld_names) for fld_names in field_names.values()]) - if n_itp_flds!=0: - all_fields = np.empty(n_vtx*n_itp_flds, dtype=np.double) - PDM.read_solb(bytes(out_files['fld']), n_vtx, n_itp_flds, all_fields) - - i_fld = 0 - for container_name, fld_names in field_names.items(): - fs = PT.new_FlowSolution(container_name, loc='Vertex', parent=dist_zone) - for fld_name in fld_names: - # Deinterlace + select distributed section since everything has been read ... - data = all_fields[i_fld::n_itp_flds][distrib_vtx[0]:distrib_vtx[1]] - PT.new_DataArray(fld_name, data, parent=fs) - i_fld += 1 - - # > Add FlowSolution for vtx tag - fs_vtx_tag = PT.new_FlowSolution('maia_topo', loc='Vertex', fields={'vtx_tag':vtx_tag}, parent=dist_zone) - - return dist_tree - - -def meshb_to_cgns(out_files, tree_info, comm): - ''' - Reading a meshb file and conversion to CGNS norm. - - Arguments : - - out_files (dict): meshb file names - - tree_info (dict): initial dist_tree informations (bc_infos, interpolated field names) - - comm (MPI) : MPI Communicator - ''' - mlog.info(f"Distributed read of meshb file...") - start = time.time() - - # meshb -> dmesh_nodal -> cgns - file_name = bytes(out_files["mesh"], 'utf-8') if isinstance(out_files["mesh"], str)\ - else bytes(out_files["mesh"]) - dmesh_nodal = PDM.meshb_to_dmesh_nodal(file_name, comm, 1, 1) - dist_tree = dmesh_nodal_to_cgns(dmesh_nodal, comm, tree_info, out_files) - - end = time.time() - dt_size = sum(MT.metrics.dtree_nbytes(dist_tree)) - all_dt_size = comm.allreduce(dt_size, MPI.SUM) - mlog.info(f"Read completed ({end-start:.2f} s) --" - f" Size of dist_tree for current rank is {mlog.bsize_to_str(dt_size)}" - f" (Σ={mlog.bsize_to_str(all_dt_size)})") - - return dist_tree - - - - - - -def cgns_to_meshb(dist_tree, files, metric_nodes, container_names, constraints): - ''' - Dist_tree conversion to meshb format and writing. - Arguments : - - dist_tree (CGNSTree) : dist_tree to convert - - files (dict) : file names for meshb files - - metric_nodes (str) : CGNS metric nodes - - container_names (str) : container_names to be interpolated - ''' - - dt_size = sum(MT.metrics.dtree_nbytes(dist_tree)) - mlog.info(f"Sequential write of a meshb file from a {mlog.bsize_to_str(dt_size)} dist_tree...") - start = time.time() - - # > Monodomain only for now - assert len(PT.get_all_Zone_t(dist_tree))==1 - - for zone in PT.get_all_Zone_t(dist_tree): - - # > Coordinates - cx, cy, cz = PT.Zone.coordinates(zone) - - # > Gathering elements by dimension - elmt_by_dim = list() - for elmts in PT.Zone.get_ordered_elements_per_dim(zone): - elmt_ec = [np_utils.safe_int_cast(PT.get_node_from_name(elmt, "ElementConnectivity")[1], np.int32) for elmt in elmts] - - if(len(elmt_ec) > 1): - elmt_by_dim.append(np.concatenate(elmt_ec)) - elif len(elmt_ec) == 1: - elmt_by_dim.append(elmt_ec[0]) - else: - elmt_by_dim.append(np.empty(0,dtype=np.int32)) - - n_tetra = elmt_by_dim[3].size // 4 - n_tri = elmt_by_dim[2].size // 3 - n_edge = elmt_by_dim[1].size // 2 - n_vtx = PT.Zone.n_vtx(zone) - - constraint_tags = {'CellCenter':[], - 'FaceCenter':[], - 'EdgeCenter':[]} - - # > PointList BC to BC tag - def bc_pl_to_bc_tag(list_of_bc, bc_tag, offset): - for n_tag, bc_n in enumerate(list_of_bc): - pl = PT.get_value(PT.get_node_from_name(bc_n, 'PointList'))[0] - bc_tag[pl-offset-1] = n_tag + 1 - bc_name = PT.get_name(bc_n) - bc_loc = PT.Subset.GridLocation(bc_n) - if constraints is not None and\ - PT.get_name(bc_n) not in constraints: - constraint_tags[bc_loc].append(str(n_tag + 1)) - - def bc_pl_to_bc_tag_vtx(list_of_bc, bc_tag, offset): - for n_tag, bc_n in enumerate(list_of_bc): - pl = PT.get_value(PT.get_node_from_name(bc_n, 'PointList'))[0] - bc_tag[pl-offset-1] = pl - - zone_bc = PT.get_child_from_label(zone, 'ZoneBC_t') - - tetra_tag = np.zeros(n_tetra , dtype=np.int32) - tri_tag = -np.ones (n_tri , dtype=np.int32) - edge_tag = -np.ones (n_edge, dtype=np.int32) - vtx_tag = np.zeros(n_vtx , dtype=np.int32) - if zone_bc is not None: - # > Cell BC_t - is_cell_bc = lambda n :PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n) == "CellCenter" - cell_bcs = PT.get_children_from_predicate(zone_bc, is_cell_bc) - n_cell_tag = bc_pl_to_bc_tag(cell_bcs, tetra_tag, 0) - - # > Face BC_t - is_face_bc = lambda n :PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n) == "FaceCenter" - face_bcs = PT.get_children_from_predicate(zone_bc, is_face_bc) - n_face_tag = bc_pl_to_bc_tag(face_bcs, tri_tag, n_tetra) - - # > Edge BC_t - is_edge_bc = lambda n :PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n) == "EdgeCenter" - edge_bcs = PT.get_children_from_predicate(zone_bc, is_edge_bc) - n_edge_tag = bc_pl_to_bc_tag(edge_bcs, edge_tag, n_tetra+n_tri) - - # > Edge BC_t - is_vtx_bc = lambda n :PT.get_label(n)=='BC_t' and PT.Subset.GridLocation(n) == "Vertex" - vtx_bcs = PT.get_children_from_predicate(zone_bc, is_vtx_bc) - n_vtx_tag = bc_pl_to_bc_tag_vtx(vtx_bcs, vtx_tag, 0) - - is_3d = n_tetra!=0 - is_2d = n_tri !=0 - if is_3d: - if (n_tri > 0 and (tri_tag < 0).any()) or (n_edge > 0 and (edge_tag < 0).any()): - raise ValueError("Some Face or Edge elements do not belong to any BC") - elif is_2d: - if (n_edge > 0 and (edge_tag < 0).any()): - raise ValueError("Some Face or Edge elements do not belong to any BC") - else: - raise ValueError("No tetrahedron or triangle Elements_t node could be found") - - - # > Write meshb - xyz = np_utils.interweave_arrays([cx,cy,cz]) - # tetra_tag = np.zeros(n_tetra, dtype=np.int32) - file_name = bytes(files["mesh"], 'utf-8') if isinstance(files["mesh"], str)\ - else bytes(files["mesh"]) - PDM.write_meshb(file_name, - n_vtx, n_tetra, n_tri, n_edge, - xyz, vtx_tag, - elmt_by_dim[3], tetra_tag, - elmt_by_dim[2], tri_tag, - elmt_by_dim[1], edge_tag) - - n_metric_fld = len(metric_nodes) - if n_metric_fld==1: - metric_fld = PT.get_value(metric_nodes[0]) - PDM.write_solb(bytes(files["sol"]), n_vtx, 1, metric_fld) - elif n_metric_fld==6: - mxx = PT.get_value(metric_nodes[0]) - mxy = PT.get_value(metric_nodes[1]) - mxz = PT.get_value(metric_nodes[2]) - myy = PT.get_value(metric_nodes[3]) - myz = PT.get_value(metric_nodes[4]) - mzz = PT.get_value(metric_nodes[5]) - met = np_utils.interweave_arrays([mxx,mxy,myy,mxz,myz,mzz]) - PDM.write_matsym_solb(bytes(files["sol"]), n_vtx, met) - - - # > Fields to interpolate - fields_list = list() - for container_name in container_names: - container = PT.get_node_from_name(zone, container_name) - fields_list += [PT.get_value(n) for n in PT.get_children_from_label(container, 'DataArray_t')] - if len(fields_list)>0: - fields_array = np_utils.interweave_arrays(fields_list) - PDM.write_solb(bytes(files["fld"]), n_vtx, len(fields_list), fields_array) - - - end = time.time() - mlog.info(f"Write of meshb file completed ({end-start:.2f} s)") - - return constraint_tags - - diff --git a/maia/io/part_tree.py b/maia/io/part_tree.py deleted file mode 100644 index 86795709..00000000 --- a/maia/io/part_tree.py +++ /dev/null @@ -1,197 +0,0 @@ -import os -import maia -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia.utils.logging as mlog -from maia.factory.dist_from_part import discover_nodes_from_matching -from maia.factory.partitioning import compute_nosplit_weights - -from .cgns_io_tree import write_tree - -def enforce_maia_naming(part_tree, comm): - """Rename the zones and joins of a partitionned tree such that maia - convention are respected - """ - old_to_new = {} - count = {} - for zone_path in PT.predicates_to_paths(part_tree, 'CGNSBase_t/Zone_t'): - prefix = MT.conv.get_part_prefix(zone_path) - if not prefix in count: - count[prefix] = 0 - part_id = count[prefix] - count[prefix] += 1 - old_to_new[zone_path] = MT.conv.add_part_suffix(prefix, comm.Get_rank(), part_id) - - MT.rename_zones(part_tree, old_to_new, comm) - - # Update JNs name for internal joins - is_intra_gc = lambda n : PT.get_label(n) in ['GridConnectivity_t', 'GridConnectivity1to1_t'] \ - and MT.conv.is_intra_gc(PT.get_name(n)) - gc_predicates = ['CGNSBase_t', 'Zone_t', 'ZoneGridConnectivity_t', is_intra_gc] - for _, zone, _, gc in PT.get_children_from_predicates(part_tree, gc_predicates, ancestors=True): - cur_proc, cur_part = MT.conv.get_part_suffix(PT.get_name(zone)) - opp_proc, opp_part = MT.conv.get_part_suffix(PT.get_value(gc)) - PT.set_name(gc, MT.conv.name_intra_gc(cur_proc, cur_part, opp_proc, opp_part)) - donor_name = PT.get_node_from_name(gc, 'GridConnectivityDonorName') - if donor_name is not None: - PT.set_value(donor_name, MT.conv.name_intra_gc(opp_proc, opp_part, cur_proc, cur_part)) - - -def _read_part_from_name(tree, filename, comm): - zones_path = PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t') - max_proc = max([PT.maia.conv.get_part_suffix(path)[0] for path in zones_path]) + 1 - if max_proc != comm.Get_size(): - mlog.error(f"Reading with {comm.Get_size()} procs file {filename} written for {max_proc} procs") - return [path for path in zones_path if PT.maia.conv.get_part_suffix(path)[0] == comm.Get_rank()] - -def _read_part_from_size(tree, filename, comm): - zones_path = PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t') - max_proc = max([PT.maia.conv.get_part_suffix(path)[0] for path in zones_path]) + 1 - mlog.warning(f"Ignoring procs affectation when reading file {filename} written for {max_proc} procs") - return [path for path in compute_nosplit_weights(tree, comm)] - - -def read_part_tree(filename, comm, redispatch=False, legacy=False): - """Read the partitioned zones from a hdf container and affect them - to the ranks. - - If ``redispatch == False``, the CGNS zones are affected to the - rank indicated in their name. - The size of the MPI communicator must thus be equal to the highest id - appearing in partitioned zone names. - - If ``redispatch == True``, the CGNS zones are dispatched over the - available processes, and renamed to follow maia's conventions. - - Args: - filename (str) : Path of the file - comm (MPIComm) : MPI communicator - redispatch (bool) : Controls the affectation of the partitions to the available ranks (see above). - Defaults to False. - Returns: - CGNSTree: Partitioned CGNS tree - - """ - - # Skeleton - if legacy: - import Converter.Filter as Filter - tree = Filter.convertFile2SkeletonTree(filename, maxDepth=2) - else: - from maia.io.cgns_io_tree import load_size_tree - from h5py import h5f - from .hdf._hdf_cgns import open_from_path, _load_node_partial - tree = load_size_tree(filename, comm) - - if redispatch: - zones_to_read = _read_part_from_size(tree, filename, comm) - else: - zones_to_read = _read_part_from_name(tree, filename, comm) - - # Data - if legacy: - to_read = list() #Read owned zones and metadata at Base level - for zone_path in PT.predicates_to_paths(tree, 'CGNSBase_t/Zone_t'): - if zone_path in zones_to_read: - to_read.append(zone_path) - PT.rm_nodes_from_label(tree, 'Zone_t') - for other_path in PT.predicates_to_paths(tree, 'CGNSBase_t/*'): - to_read.append(other_path) - for base in PT.get_children_from_label(tree, 'CGNSBase_t'): - PT.set_children(base, []) #Remove Base children to append it (with data) after - - nodes = Filter.readNodesFromPaths(filename, to_read) - for path, node in zip(to_read, nodes): - base = PT.get_node_from_path(tree, PT.path_head(path)) - PT.add_child(base, node) - else: - # Remove zones not going to this rank - for base in PT.get_children_from_label(tree, 'CGNSBase_t'): - _zones_to_read = [PT.path_tail(zpath) for zpath in zones_to_read if PT.path_head(zpath) == PT.get_name(base)] - PT.rm_children_from_predicate(base, lambda n: PT.get_label(n) == 'Zone_t' and PT.get_name(n) not in _zones_to_read) - - # Now load full data of affected zones - fid = h5f.open(bytes(filename, 'utf-8'), h5f.ACC_RDONLY) - for base in PT.get_children_from_label(tree, 'CGNSBase_t'): - zone_names = [PT.get_name(n) for n in PT.get_children_from_label(base, 'Zone_t')] - PT.rm_children_from_label(base, 'Zone_t') - for zone_name in zone_names: - gid = open_from_path(fid, f'{PT.get_name(base)}/{zone_name}') - _load_node_partial(gid, base, lambda X,Y:True, ([],[])) - gid.close() - fid.close() - - # Remove empty bases - PT.rm_children_from_predicate(tree, lambda n: PT.get_label(n) == 'CGNSBase_t' \ - and len(PT.get_children_from_label(n, 'Zone_t')) == 0) - - if redispatch: - enforce_maia_naming(tree, comm) - - return tree - - -def save_part_tree(part_tree, filename, comm, single_file=False, legacy=False): - """Gather the partitioned zones managed by all the processes and write it in a unique - hdf container. - - If ``single_file`` is True, one file named *filename* storing all the partitioned - zones is written. Otherwise, hdf links are used to produce a main file *filename* - linking to additional subfiles. - - Args: - part_tree (CGNSTree) : Partitioned tree - filename (str) : Path of the output file - comm (MPIComm) : MPI communicator - single_file (bool) : Produce a unique file if True; use CGNS links otherwise. - - Example: - .. literalinclude:: snippets/test_io.py - :start-after: #save_part_tree@start - :end-before: #save_part_tree@end - :dedent: 2 - """ - rank = comm.Get_rank() - base_name, extension = os.path.splitext(filename) - subfilename = base_name + f'_sub_{rank}' + extension - - # Recover base data and families - top_tree = PT.new_CGNSTree() - discover_nodes_from_matching(top_tree, [part_tree], 'CGNSBase_t', comm, get_value='all', child_list=['Family_t', 'ReferenceState_t']) - - if single_file: - # Sequential write seems to be faster than collective io -- see 01d84da7 for other methods - # Create file and write Bases - if rank == 0: - write_tree(top_tree, filename, legacy=legacy) - comm.barrier() - for i in range(comm.Get_size()): - if i == rank: - if legacy: - from Converter.Distributed import writeZones - writeZones(part_tree, filename, proc=-1) - else: - from h5py import h5f - from .hdf._hdf_cgns import open_from_path, _write_node_partial - fid = h5f.open(bytes(filename, 'utf-8'), h5f.ACC_RDWR) - for zone_path in maia.pytree.predicates_to_paths(part_tree, 'CGNSBase_t/Zone_t'): - zone = PT.get_node_from_path(part_tree, zone_path) - gid = open_from_path(fid, zone_path.split('/')[0]) - _write_node_partial(gid, zone, lambda X,Y: True, ([],[])) - gid.close() - fid.close() - comm.barrier() - - else: - links = [] - for zone_path in maia.pytree.predicates_to_paths(part_tree, 'CGNSBase_t/Zone_t'): - links += [['', subfilename, zone_path, zone_path]] - - write_tree(part_tree, subfilename, legacy=legacy) #Use direct API to manage name - - _links = comm.gather(links, root=0) - if rank == 0: - links = [l for proc_links in _links for l in proc_links] #Flatten gather result - write_tree(top_tree, filename, links=links, legacy=legacy) - diff --git a/maia/io/test/test_cgns_io_tree.py b/maia/io/test/test_cgns_io_tree.py deleted file mode 100644 index f71a1dc8..00000000 --- a/maia/io/test/test_cgns_io_tree.py +++ /dev/null @@ -1,70 +0,0 @@ -import pytest -import pytest_parallel - -import maia.io -import maia.pytree as PT - -from maia.pytree.yaml import parse_yaml_cgns -import maia.utils.test_utils as TU - -import os - -@pytest_parallel.mark.parallel(1) -def test_dist_tree_to_file_1proc(comm): - yt = """ -Base CGNSBase_t I4 [3, 3]: - Zone Zone_t I4 [[4, 0, 0]]: - ZoneType ZoneType_t 'Unstructured': - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 1., 2., 3.]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t I4 [0, 4, 4]: - Cell DataArray_t I4 [0, 0, 0]: -""" - - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - - tmp_dir = TU.create_collective_tmp_dir(comm) - out_file = os.path.join(tmp_dir, 'yt.cgns') - maia.io.dist_tree_to_file(dist_tree, out_file, comm) - - t = maia.io.cgns_io_tree.read_tree(out_file) - assert (PT.get_value(PT.get_node_from_name(t,"CoordinateX")) == [0.,1.,2.,3.]).all() - TU.rm_collective_dir(tmp_dir, comm) - - -@pytest_parallel.mark.parallel(2) -def test_dist_tree_to_file_2procs(comm): - if comm.Get_rank()==0: - yt = """ -Base CGNSBase_t I4 [3, 3]: - Zone Zone_t I4 [[4, 0, 0]]: - ZoneType ZoneType_t 'Unstructured': - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [0., 1.]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t I4 [0, 2, 4]: - Cell DataArray_t I4 [0, 0, 0]: -""" - else: - yt = """ -Base CGNSBase_t I4 [3, 3]: - Zone Zone_t I4 [[4, 0, 0]]: - ZoneType ZoneType_t 'Unstructured': - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [2., 3.]: - :CGNS#Distribution UserDefinedData_t: - Vertex DataArray_t I4 [2, 4, 4]: - Cell DataArray_t I4 [0, 0, 0]: -""" - - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - - tmp_dir = TU.create_collective_tmp_dir(comm) - out_file = os.path.join(tmp_dir, 'yt.cgns') - maia.io.dist_tree_to_file(dist_tree, out_file, comm) - - if comm.Get_rank()==0: - t = maia.io.cgns_io_tree.read_tree(out_file) - assert (PT.get_value(PT.get_node_from_name(t,"CoordinateX")) == [0.,1.,2.,3.]).all() - TU.rm_collective_dir(tmp_dir, comm) diff --git a/maia/io/test/test_distribution_tree.py b/maia/io/test/test_distribution_tree.py deleted file mode 100644 index cd40e4c1..00000000 --- a/maia/io/test/test_distribution_tree.py +++ /dev/null @@ -1,166 +0,0 @@ -import pytest_parallel -import mpi4py.MPI as MPI -import numpy as np - -import maia.pytree as PT -import maia.pytree.maia as MT - -from maia.pytree.yaml import parse_yaml_cgns -from maia.utils.parallel import utils as par_utils -from maia.io import distribution_tree - -@pytest_parallel.mark.parallel(2) -def test_compute_subset_distribution(comm): - node = PT.new_BC(name='BC', point_range=[[1,3],[1,3],[3,3]]) - distribution_tree.compute_subset_distribution(node, comm, par_utils.uniform_distribution) - - distrib_ud = MT.getDistribution(node) - assert PT.get_label(distrib_ud) == 'UserDefinedData_t' - distrib = PT.get_child_from_name(distrib_ud, 'Index') - assert PT.get_label(distrib) == 'DataArray_t' - assert (PT.get_value(distrib) == par_utils.uniform_distribution(3*3*1, comm)).all() - - node = PT.new_BC(name='BC') - PT.new_IndexArray('PointList', None, parent=node) - PT.new_IndexArray('PointList#Size', [1,9], parent=node) - distribution_tree.compute_subset_distribution(node, comm, par_utils.uniform_distribution) - - distrib_ud = MT.getDistribution(node) - assert PT.get_label(distrib_ud) == 'UserDefinedData_t' - distrib = PT.get_child_from_name(distrib_ud, 'Index') - assert PT.get_label(distrib) == 'DataArray_t' - assert (PT.get_value(distrib) == par_utils.uniform_distribution(1*9, comm)).all() - -@pytest_parallel.mark.parallel(2) -def test_compute_elements_distribution(comm): - zoneS = PT.new_Zone('ZoneS', type='Structured') - zoneU = PT.new_Zone('ZoneS', type='Unstructured') - hexa = PT.new_Elements('Hexa', 'HEXA_8', erange=[1,100],parent=zoneU) - tri = PT.new_Elements('Tri', 'TRI_3', erange=[101,1000],parent=zoneU) - distribution_tree.compute_elements_distribution(zoneS, comm, par_utils.uniform_distribution) - distribution_tree.compute_elements_distribution(zoneU, comm, par_utils.uniform_distribution) - assert (PT.get_node_from_name(hexa, 'Element')[1] == \ - par_utils.uniform_distribution(100, comm)).all() - assert (PT.get_node_from_name(tri , 'Element')[1] == \ - par_utils.uniform_distribution(900, comm)).all() - assert PT.get_node_from_name(zoneS, 'Element') == None - - - -@pytest_parallel.mark.parallel(2) -class Test_compute_zone_distribution: - def test_unstruct(self, comm): - yt = """ -Zone Zone_t [[27,8,0]]: - ZoneType ZoneType_t "Unstructured": - Ngon Elements_t [22,0]: - ElementRange IndexArray_t [1,36]: - ZBC ZoneBC_t: - bc1 BC_t "Farfield": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,4]: - bcds BCDataSet_t: - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,2]: - ZGC ZoneGridConnectivity_t: - match GridConnectivity_t "otherzone": - PointList IndexArray_t None: - PointListDonor IndexArray_t None: - PointList#Size IndexArray_t [1,4]: - ZSR ZoneSubRegion_t: - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,12]: - FS FlowSolution_t: - PointList IndexArray_t None: - PointList#Size IndexArray [1,10]: - """ - zone = parse_yaml_cgns.to_node(yt) - distribution_tree.compute_zone_distribution(zone, comm, par_utils.uniform_distribution) - assert len(PT.get_nodes_from_name(zone, 'Index')) == 5 - assert len(PT.get_nodes_from_name(zone, 'Element')) == 1 - - def test_struct(self, comm): - yt = """ -Zone Zone_t [[3,3,3],[2,2,2],[0,0,0]]: - ZoneType ZoneType_t "Structured": - ZBC ZoneBC_t: - bc1 BC_t "Farfield": - PointRange IndexRange_t [[1,3],[1,3],[1,1]]: - bcds BCDataSet_t: - PointRange IndexRange_t [[1,3],[1,1],[1,1]]: - ZSR ZoneSubRegion_t: - PointRange IndexRange_t [[2,2],[2,2],[1,1]]: - """ - zone = parse_yaml_cgns.to_node(yt) - distribution_tree.compute_zone_distribution(zone, comm, par_utils.uniform_distribution) - assert PT.get_node_from_name(zone, 'PointList#Size') is None - assert len(PT.get_nodes_from_name(zone, 'Index')) == 3 - assert MT.getDistribution(zone, 'Face') is not None - - -@pytest_parallel.mark.parallel(2) -def test_add_distribution_info(comm): - dist_tree = parse_yaml_cgns.to_cgns_tree(""" -Base CGNSBase_t [3,3]: - ZoneU Zone_t [[27,8,0]]: - ZoneType ZoneType_t "Unstructured": - Ngon Elements_t [22,0]: - ElementRange IndexArray_t [1,36]: - ZBC ZoneBC_t: - bc1 BC_t "Farfield": - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,4]: - bcds BCDataSet_t: - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,2]: - ZGC ZoneGridConnectivity_t: - match GridConnectivity_t "otherzone": - PointList IndexArray_t None: - PointListDonor IndexArray_t None: - PointList#Size IndexArray_t [1,4]: - ZSR ZoneSubRegion_t: - PointList IndexArray_t None: - PointList#Size IndexArray_t [1,12]: - ZoneS Zone_t [[3,3,3],[2,2,2],[0,0,0]]: - ZoneType ZoneType_t "Structured": - ZBC ZoneBC_t: - bc1 BC_t "Farfield": - PointRange IndexRange_t [[1,3],[1,3],[1,1]]: - bcds BCDataSet_t: - PointRange IndexRange_t [[1,3],[1,1],[1,1]]: - ZSR ZoneSubRegion_t: - PointRange IndexRange_t [[2,2],[2,2],[1,1]]: -""") - distribution_tree.add_distribution_info(dist_tree, comm) - assert len(PT.get_nodes_from_name(dist_tree, 'Index')) == 4+3 - -def test_clean_distribution_info(): - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneA Zone_t [[27],[8],[0]]: - Ngon Elements_t [22,0]: - ElementConnectivity DataArray_t [1,2,3,4]: - :CGNS#Distribution UserDefinedData_t: - ZBC ZoneBC_t: - bc1 BC_t "Farfield": - PointList IndexArray_t [[1,2]]: - :CGNS#Distribution UserDefinedData_t: - Index DataArray_t [0,2,4]: - bcds BCDataSet_t: - :CGNS#Distribution UserDefinedData_t: - ZGC ZoneGridConnectivity_t: - matchAB GridConnectivity_t "ZoneB": - GridLocation GridLocation_t "FaceCenter": - PointList IndexArray_t [1,4,7,10]: - PointListDonor IndexArray_t [13,16,7,10]: - :CGNS#Distribution UserDefinedData_t: - FS FlowSolution_t: - field DataArray_t: - :CGNS#Distribution UserDefinedData_t: - :CGNS#Distribution UserDefinedData_t: -""" - dist_tree = parse_yaml_cgns.to_cgns_tree(yt) - distribution_tree.clean_distribution_info(dist_tree) - assert PT.get_node_from_name(dist_tree, ':CGNS#Distribution') is None - assert len(PT.get_nodes_from_name(dist_tree, 'PointList')) == 2 - diff --git a/maia/io/test/test_fix_tree.py b/maia/io/test/test_fix_tree.py deleted file mode 100644 index 2985e19e..00000000 --- a/maia/io/test/test_fix_tree.py +++ /dev/null @@ -1,281 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np - -import maia -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns -from maia import npy_pdm_gnum_dtype as pdm_dtype -from maia.utils import logging as mlog - -from maia.io import fix_tree - -class log_capture: - def __init__(self): - self.logs = '' - def log(self, msg): - self.logs += msg - -def test_check_datasize(): - yt = """ - Base CGNSBase_t [3,3]: - ZoneA Zone_t I4 [[11,10,0]]: - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t: - """ - log_collector = log_capture() - mlog.add_printer_to_logger('maia-warnings', log_collector) - - tree = parse_yaml_cgns.to_cgns_tree(yt) - fix_tree.check_datasize(tree) - assert log_collector.logs == '' - grid_co = PT.get_node_from_name(tree, 'GridCoordinates') - PT.new_DataArray('CoordinateY', np.arange(1000), parent=grid_co) - fix_tree.check_datasize(tree) - assert "Some heavy data are not distributed: ['CoordinateY']\n" in log_collector.logs - -def test_fix_zone_datatype(): - yt = """ - Base CGNSBase_t [3,3]: - ZoneA Zone_t I4 [[11,10,0]]: - ZoneB Zone_t I4 [[11,10,0]]: - """ - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - size_data = {'/CGNSLibraryVersion': (1, 'R4', (1,)), - '/Base': (1, 'I4', (2,)), - '/Base/ZoneA': (1, 'I4', (1, 3)), - '/Base/ZoneB': (1, 'I8', (1, 3))} - fix_tree.fix_zone_datatype(size_tree, size_data) - assert PT.get_node_from_name(size_tree, "ZoneA")[1].dtype == np.int32 - assert PT.get_node_from_name(size_tree, "ZoneB")[1].dtype == np.int64 - -def test_fix_point_ranges(): - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneA Zone_t: - ZGC ZoneGridConnectivity_t: - matchAB GridConnectivity1to1_t "ZoneB": - PointRange IndexRange_t [[17,17],[3,9],[1,5]]: - PointRangeDonor IndexRange_t [[7,1],[9,9],[1,5]]: - Transform "int[IndexDimension]" [-2,-1,-3]: - ZoneB Zone_t: - ZGC ZoneGridConnectivity_t: - matchBA GridConnectivity1to1_t "Base0/ZoneA": - PointRange IndexRange_t [[7,1],[9,9],[1,5]]: - PointRangeDonor IndexRange_t [[17,17],[3,9],[1,5]]: - Transform "int[IndexDimension]" [-2,-1,-3]: -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - fix_tree.fix_point_ranges(size_tree) - gcA = PT.get_node_from_name(size_tree, 'matchAB') - gcB = PT.get_node_from_name(size_tree, 'matchBA') - assert (PT.get_child_from_name(gcA, 'PointRange')[1] == [[17,17], [3,9], [1,5]]).all() - assert (PT.get_child_from_name(gcA, 'PointRangeDonor')[1] == [[ 7, 1], [9,9], [5,1]]).all() - assert (PT.get_child_from_name(gcB, 'PointRange')[1] == [[ 7, 1], [9,9], [5,1]]).all() - assert (PT.get_child_from_name(gcB, 'PointRangeDonor')[1] == [[17,17], [3,9], [1,5]]).all() - -def test_ensure_symmetric_gc1to1(): - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneA Zone_t: - ZGC ZoneGridConnectivity_t: - matchAB GridConnectivity1to1_t "ZoneB": - PointRange IndexRange_t [[17,17],[3,9],[1,5]]: - PointRangeDonor IndexRange_t [[7,1],[9,9],[1,5]]: - ZoneB Zone_t: - ZGC ZoneGridConnectivity_t: - matchBA GridConnectivity1to1_t "Base0/ZoneA": - PointRange IndexRange_t [[1,7],[9,9],[1,5]]: - PointRangeDonor IndexRange_t [[17,17],[9,3],[1,5]]: -""" - tree = parse_yaml_cgns.to_cgns_tree(yt) - fix_tree.ensure_symmetric_gc1to1(tree) - gcA = PT.get_node_from_name(tree, 'matchAB') - gcB = PT.get_node_from_name(tree, 'matchBA') - assert (PT.get_child_from_name(gcA, 'PointRange')[1] == [[17,17], [3,9], [1,5]]).all() - assert (PT.get_child_from_name(gcA, 'PointRangeDonor')[1] == [[ 7, 1], [9,9], [1,5]]).all() - assert (PT.get_child_from_name(gcB, 'PointRange')[1] == PT.get_child_from_name(gcA, 'PointRangeDonor')[1]).all() - assert (PT.get_child_from_name(gcB, 'PointRangeDonor')[1] == PT.get_child_from_name(gcA, 'PointRange')[1]).all() - -def test_add_missing_pr_in_dataset(): - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneA Zone_t: - ZoneType ZoneType_t "Structured": - ZBC ZoneBC_t: - BCA1 BC_t: - PointRange IndexRange_t [[1,1],[1,29],[1,85]]: - BCDS BCDataSet_t: - GridLocation GridLocation_t "FaceCenter": - BCA2 BC_t: - PointRange IndexRange_t [[1,1],[1,29],[1,85]]: - BCDS BCDataSet_t: - GridLocation GridLocation_t "IFaceCenter": - BCA3 BC_t: - PointRange IndexRange_t [[1,29],[1,1],[1,85]]: - BCDS BCDataSet_t: - GridLocation GridLocation_t "JFaceCenter": - BCA4 BC_t: - PointRange IndexRange_t [[1,29],[1,85],[1,1]]: - BCDS BCDataSet_t: - GridLocation GridLocation_t "KFaceCenter": - BCB BC_t: - PointRange IndexRange_t [[1,1],[1,3],[1,2]]: - GridLocation GridLocation_t "FaceCenter": - BCDS BCDataSet_t: - GridLocation GridLocation_t "FaceCenter": - BCC BC_t: - PointRange IndexRange_t [[1,1],[1,3],[1,2]]: - BCDS BCDataSet_t: - ZoneB Zone_t: - ZoneType ZoneType_t "Unstructured": - ZBC ZoneBC_t: - BC BC_t: - PointRange IndexRange_t [[1,1],[1,3],[1,2]]: - BCDS BCDataSet_t: - GridLocation GridLocation_t "FaceCenter": -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - fix_tree.add_missing_pr_in_bcdataset(size_tree) - bcA1 = PT.get_node_from_name(size_tree, 'BCA1') - bcdsA1 = PT.get_child_from_label(bcA1, 'BCDataSet_t') - assert (PT.get_child_from_name(bcdsA1, 'PointRange')[1] == [[1,1], [1,28], [1,84]]).all() - bcA2 = PT.get_node_from_name(size_tree, 'BCA2') - bcdsA2 = PT.get_child_from_label(bcA2, 'BCDataSet_t') - assert (PT.get_child_from_name(bcdsA2, 'PointRange')[1] == [[1,1], [1,28], [1,84]]).all() - bcA3 = PT.get_node_from_name(size_tree, 'BCA3') - bcdsA3 = PT.get_child_from_label(bcA3, 'BCDataSet_t') - assert (PT.get_child_from_name(bcdsA3, 'PointRange')[1] == [[1,28], [1,1], [1,84]]).all() - bcA4 = PT.get_node_from_name(size_tree, 'BCA4') - bcdsA4 = PT.get_child_from_label(bcA4, 'BCDataSet_t') - assert (PT.get_child_from_name(bcdsA4, 'PointRange')[1] == [[1,28], [1,84], [1,1]]).all() - bcB = PT.get_node_from_name(size_tree, 'BCB') - bcdsB = PT.get_child_from_label(bcB, 'BCDataSet_t') - assert (PT.get_child_from_name(bcdsB, 'PointRange') is None) - bcC = PT.get_node_from_name(size_tree, 'BCB') - bcdsC = PT.get_child_from_label(bcC, 'BCDataSet_t') - assert (PT.get_child_from_name(bcdsC, 'PointRange') is None) - bc = PT.get_node_from_name(size_tree, 'BCB') - bcds = PT.get_child_from_label(bc, 'BCDataSet_t') - assert (PT.get_child_from_name(bcds, 'PointRange') is None) - -def test_enforce_pdm_dtype(): - wrong_pdm_type = np.int64 if pdm_dtype == np.int32 else np.int32 - wrong_type = 'I8' if pdm_dtype == np.int32 else 'I4' - yt = f""" - Base CGNSBase_t [3,3]: - Zone Zone_t {wrong_type} [[11,10,0]]: - NGon Elements_t [22,0]: - ElementRange IndexRange_t [1, 3]: - ElementConnectivity DataArray_t {wrong_type} [1,2,3,4]: - ElementStartOffset DataArray_t {wrong_type} [0,1,2]: - ZGC ZoneGridConnectivity_t: - match GridConnectivity_t "ZoneB": - PointList IndexArray_t {wrong_type} [[11,12,13]]: - PointListDonor IndexArray_t {wrong_type} [[1,2,3]]: - """ - tree = parse_yaml_cgns.to_cgns_tree(yt) - assert PT.get_node_from_name(tree, 'PointList')[1].dtype == wrong_pdm_type - assert PT.get_node_from_name(tree, 'ElementConnectivity')[1].dtype == wrong_pdm_type - assert PT.get_node_from_name(tree, 'ElementStartOffset')[1].dtype == wrong_pdm_type - assert PT.get_node_from_name(tree, 'ElementRange')[1].dtype == np.int32 - fix_tree._enforce_pdm_dtype(tree) - assert PT.get_node_from_name(tree, 'PointList')[1].dtype == pdm_dtype - assert PT.get_node_from_name(tree, 'ElementConnectivity')[1].dtype == pdm_dtype - assert PT.get_node_from_name(tree, 'ElementStartOffset')[1].dtype == pdm_dtype - assert PT.get_node_from_name(tree, 'ElementRange')[1].dtype == pdm_dtype - -def test_ensure_PE_global_indexing(): - - def create_tree(elts): - tree = PT.new_CGNSTree() - base = PT.new_CGNSBase(parent=tree) - zone = PT.new_Zone(type='Unstructured', parent=base) - for elt in elts: - PT.add_child(zone, elt) - return tree - - ngon = PT.new_Elements('WrongNGon', 'NGON_n', erange=[1,4]) - pe = PT.new_DataArray('ParentElements', [[1,2],[3,0],[1,0],[2,4]], parent=ngon) - fix_tree.ensure_PE_global_indexing(create_tree([ngon])) - assert (pe[1] == [[5,6],[7,0],[5,0],[6,8]]).all() - - ngon = PT.new_Elements('GoodNGon', 'NGON_n', erange=[1,4]) - pe = PT.new_DataArray('ParentElements', [[5,6],[7,0],[5,0],[6,8]], parent=ngon) - fix_tree.ensure_PE_global_indexing(create_tree([ngon])) - assert (pe[1] == [[5,6],[7,0],[5,0],[6,8]]).all() - - nface = PT.new_Elements('FirstNace', 'NFACE_n', erange=[1,2]) - ngon = PT.new_Elements('SecondNGon', 'NGON_n', erange=[3,6]) - pe = PT.new_DataArray('ParentElements', [[1,0],[1,0],[1,2],[2,0]], parent=ngon) - fix_tree.ensure_PE_global_indexing(create_tree([ngon])) - assert (pe[1] == [[1,0],[1,0],[1,2],[2,0]]).all() - - ngon = PT.new_Elements('EmptyNGon', 'NGON_n', erange=[1,4]) - pe = PT.new_DataArray('ParentElements', np.empty((0,2), order='F'), parent=ngon) - fix_tree.ensure_PE_global_indexing(create_tree([ngon])) - - with pytest.raises(RuntimeError): - ngon = PT.new_Elements('NGon', 'NGON_n') - fix_tree.ensure_PE_global_indexing(create_tree([ngon, ngon])) - with pytest.raises(RuntimeError): - ngon = PT.new_NGonElements(erange=[1,4], pe=np.empty((4,2), order='F')) - tri = PT.new_Elements('Tri', 'TRI_3') - fix_tree.ensure_PE_global_indexing(create_tree([ngon, tri])) - -@pytest_parallel.mark.parallel(1) -def test_ensure_signed_nface_connectivity(comm): - tree = maia.factory.generate_dist_block(3, 'Poly', comm) - maia.algo.pe_to_nface(tree, comm) - tree_bck = PT.deep_copy(tree) - - assert fix_tree.ensure_signed_nface_connectivity(tree, comm) == 0 - assert PT.is_same_tree(tree, tree_bck) - - # Force unsigned connectivity - nface_ec = PT.get_node_from_path(tree, 'Base/zone/NFaceElements/ElementConnectivity') - nface_ec[1] = np.abs(nface_ec[1]) - assert fix_tree.ensure_signed_nface_connectivity(tree, comm) == 1 - assert PT.is_same_tree(tree, tree_bck) - -def test_rm_legacy_nodes(): - yt = f""" - ZoneA Zone_t [[11,10,0]]: - ZoneType ZoneType_t "Unstructured": - :elsA#Hybrid UserDefinedData_t: - SortedCrossTable DataArray_t: - IndexNGONCrossTable DataArray_t: - ZoneB Zone_t [[11,10,0]]: - FlowSol FlowSolution_t: - GridLocation GridLocation_t "CellCenter": - GoodArray DataArray_t: - GoodArray#Size DataArray_t [10]: - WrongArray DataArray_t: - ZoneC Zone_t [[11,10,0]]: - :elsA#Hybrid UserDefinedData_t: - """ - tree = parse_yaml_cgns.to_cgns_tree(yt) - fix_tree.rm_legacy_nodes(tree) - assert PT.get_node_from_name(tree, ':elsA#Hybrid') is None - - assert PT.get_node_from_name(tree, 'GoodArray') is not None - assert PT.get_node_from_name(tree, 'WrongArray') is None - -def test_corr_index_range_names(): - yt = """ -Base0 CGNSBase_t [3,3]: - ZoneA Zone_t: - ZBC ZoneBC_t: - BCA BC_t: - ElementRange IndexRange_t: - BCB BC_t: - WrongName IndexRange_t: -""" - size_tree = parse_yaml_cgns.to_cgns_tree(yt) - fix_tree.corr_index_range_names(size_tree) - bcA = PT.get_node_from_name(size_tree, 'BCA') - bcB = PT.get_node_from_name(size_tree, 'BCB') - irA = PT.get_node_from_label(bcA, 'IndexRange_t') - irB = PT.get_node_from_label(bcB, 'IndexRange_t') - assert PT.get_name(irA) == 'PointRange' - assert PT.get_name(irB) == 'WrongName' diff --git a/maia/io/test/test_hdf_io_cass.py b/maia/io/test/test_hdf_io_cass.py deleted file mode 100644 index 4ad42153..00000000 --- a/maia/io/test/test_hdf_io_cass.py +++ /dev/null @@ -1,94 +0,0 @@ -import pytest - -know_cassiopee = True -try: - import Converter -except ImportError: - know_cassiopee = False - -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns - -@pytest.mark.skipif(not know_cassiopee, reason="Require Cassiopee") -def test_add_sizes_to_zone_tree(): - from maia.io import _hdf_io_cass as LC - yt = """ -Zone Zone_t: - Hexa Elements_t [17, 0]: - ElementConnectivity DataArray_t None: - ZBC ZoneBC_t: - bc BC_t "farfield": - PointList IndexArray_t None: - bc_withds BC_t "farfield": - PointList IndexArray_t None: - BCDataSet BCDataSet_t: - PointList IndexArray_t None: - ZGC ZoneGridConnectivity_t: - gc GridConnectivity_t: - PointList IndexArray_t None: - PointListDonor IndexArray_t None: - ZSR ZoneSubRegion_t: - PointList IndexArray_t None: - FS FlowSolution_t: - FSPL FlowSolution_t: - PointList IndexArray_t None: -""" - zone = parse_yaml_cgns.to_node(yt) - size_data = {'/Zone/Hexa/ElementConnectivity' : (1, 'I4', 160), - '/Zone/ZBC/bc/PointList' : (1, 'I4', (1,30)), - '/Zone/ZBC/bc_withds/PointList' : (1, 'I4', (1,100)), - '/Zone/ZBC/bc_withds/BCDataSet/PointList' : (1, 'I4', (1,10)), - '/Zone/ZGC/gc/PointList' : (1, 'I4', (1,20)), - '/Zone/ZGC/gc/PointListDonor' : (1, 'I4', (1,20)), - '/Zone/ZSR/PointList' : (1, 'I4', (1,34)), - '/Zone/FSPL/PointList' : (1, 'I4', (1,10)), - } - - LC.add_sizes_to_zone_tree(zone, '/Zone', size_data) - - assert PT.get_node_from_path(zone, 'Hexa/ElementConnectivity#Size') is None - - assert (PT.get_node_from_path(zone, 'ZBC/bc/PointList#Size')[1] == [1,30]).all() - assert (PT.get_node_from_path(zone, 'ZBC/bc_withds/PointList#Size')[1] == [1,100]).all() - assert (PT.get_node_from_path(zone, 'ZBC/bc_withds/BCDataSet/PointList#Size')[1] == [1,10]).all() - - assert (PT.get_node_from_path(zone, 'ZGC/gc/PointList#Size')[1] == [1,20]).all() - - assert (PT.get_node_from_path(zone, 'ZSR/PointList#Size')[1] == [1,34]).all() - - assert (PT.get_node_from_path(zone, 'FSPL/PointList#Size')[1] == [1,10]).all() - assert (PT.get_node_from_path(zone, 'FS/PointList#Size') is None) - -@pytest.mark.skipif(not know_cassiopee, reason="Require Cassiopee") -def test_add_sizes_to_tree(): - from maia.io import _hdf_io_cass as LC - yt = """ -BaseA CGNSBase_t: - Zone Zone_t: - Hexa Elements_t [17, 0]: - ElementConnectivity DataArray_t None: - ZBC ZoneBC_t: - bc BC_t "farfield": - PointList IndexArray_t None: - bc_withds BC_t "farfield": - PointList IndexArray_t None: - BCDataSet BCDataSet_t: - PointList IndexArray_t None: - ZGC ZoneGridConnectivity_t: - gc GridConnectivity_t: - PointList IndexArray_t None: - PointListDonor IndexArray_t None: - ZSR ZoneSubRegion_t: - PointList IndexArray_t None: -""" - tree = parse_yaml_cgns.to_cgns_tree(yt) - size_data_tree = {'/BaseA/Zone/Hexa/ElementConnectivity' : (1, 'I4', 160), - '/BaseA/Zone/ZBC/bc/PointList' : (1, 'I4', (1,30)), - '/BaseA/Zone/ZBC/bc_withds/PointList' : (1, 'I4', (1,100)), - '/BaseA/Zone/ZBC/bc_withds/BCDataSet/PointList' : (1, 'I4', (1,10)), - '/BaseA/Zone/ZGC/gc/PointList' : (1, 'I4', (1,20)), - '/BaseA/Zone/ZGC/gc/PointListDonor' : (1, 'I4', (1,20)), - '/BaseA/Zone/ZSR/PointList' : (1, 'I4', (1,34)), - } - LC.add_sizes_to_tree(tree, size_data_tree) - assert len(PT.get_nodes_from_name(tree, '*#Size')) == 6 diff --git a/maia/io/test/test_hdf_io_h5py.py b/maia/io/test/test_hdf_io_h5py.py deleted file mode 100644 index 8f903e9a..00000000 --- a/maia/io/test/test_hdf_io_h5py.py +++ /dev/null @@ -1,112 +0,0 @@ -import pytest -import pytest_parallel - -import numpy as np -from pathlib import Path - -import maia.pytree as PT -from maia.pytree.yaml import parse_yaml_cgns - -import maia.utils.test_utils as TU - -from maia.io import _hdf_io_h5py as IOH - -def test_load_data(): - names, labels = 'Base/Zone', 'CGNSBase_t/Zone_t' - assert IOH.load_data(names.split('/'), labels.split('/')) == True - names, labels = 'Base/Zone/GCo/CX', 'CGNSBase_t/Zone_t/GridCoordinates_t/DataArray_t' - assert IOH.load_data(names.split('/'), labels.split('/')) == False - names, labels = 'Base/Zone/GCo/CX', 'CGNSBase_t/Zone_t/UserDefinedData_t/DataArray_t' - assert IOH.load_data(names.split('/'), labels.split('/')) == True - names, labels = 'GC/GCP/Perio/RotationAngle', 'GridConnectivity_t/GridConnectivityProperty_t/Periodic_t/DataArray_t' - assert IOH.load_data(names.split('/'), labels.split('/')) == True - names, labels = 'ZBC/BC/PointList', 'ZoneBC_t/BC_t/IndexArray_t' - assert IOH.load_data(names.split('/'), labels.split('/')) == False - names, labels = 'Base/Zone/:elsA#Hybrid/IndexNGONCrossTable', 'CGNSBase_t/Zone_t/UserDefinedData_t/DataArray_t' - assert IOH.load_data(names.split('/'), labels.split('/')) == False - names, labels = 'ZBC/BC/PointRange', 'ZoneBC_t/BC_t/IndexRange_t' - assert IOH.load_data(names.split('/'), labels.split('/')) == True - names, labels = 'FSSeq/GM/Coeff', 'FlowEquationSet_t/GasModel_t/DataArray_t' - assert IOH.load_data(names.split('/'), labels.split('/')) == True - -@pytest_parallel.mark.parallel(3) -def test_load_size_tree(comm): - filename = str(TU.sample_mesh_dir / 'only_coords.hdf') - yt = """ - Base CGNSBase_t [2,2]: - ZoneU Zone_t [[6, 0, 0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t: - CoordinateX#Size DataArray_t I8 [6]: - CoordinateY DataArray_t: - CoordinateY#Size DataArray_t I8 [6]: - ZoneS Zone_t [[2, 1, 0], [2, 1, 0]]: - ZoneType ZoneType_t 'Structured': - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t: - CoordinateX#Size DataArray_t I8 [2,2]: - CoordinateY DataArray_t: - CoordinateY#Size DataArray_t I8 [2,2]: - """ - sizetree = IOH.load_size_tree(filename, comm) - assert PT.is_same_tree(sizetree, parse_yaml_cgns.to_cgns_tree(yt)) - -def test_load_partial(): - filename = str(TU.sample_mesh_dir / 'only_coords.hdf') - yt = """ - Base CGNSBase_t [2,2]: - ZoneU Zone_t [[6, 0, 0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t: - CoordinateY DataArray_t: - """ - # Nb : Low level load/write are tested in other file - tree = parse_yaml_cgns.to_cgns_tree(yt) - hdf_filter = {'Base/ZoneU/GridCoordinates/CoordinateX' : [[0], [1], [2], [1], [4], [1], [2], [1], [6], [1]]} - IOH.load_partial(filename, tree, hdf_filter) - assert np.allclose(PT.get_node_from_name(tree, 'CoordinateX')[1], [5., 6.]) - -@pytest_parallel.mark.parallel(2) -def test_write_partial(comm, tmp_path): - if comm.rank == 0: - yt = """ - Base CGNSBase_t [2,2]: - ZoneU Zone_t [[6, 0, 0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [1,2,3,4]: - CoordinateY DataArray_t R8 [-1,-2,-3,-4]: - """ - hdf_filter = {'Base/ZoneU/GridCoordinates/CoordinateX' : [[0], [1], [4], [1], [0], [1], [4], [1], [6], [1]], - 'Base/ZoneU/GridCoordinates/CoordinateY' : [[0], [1], [4], [1], [0], [1], [4], [1], [6], [1]]} - else: - yt = """ - Base CGNSBase_t [2,2]: - ZoneU Zone_t [[6, 0, 0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [5,6]: - CoordinateY DataArray_t R8 [-5,-6]: - """ - hdf_filter = {'Base/ZoneU/GridCoordinates/CoordinateX' : [[0], [1], [2], [1], [4], [1], [2], [1], [6], [1]], - 'Base/ZoneU/GridCoordinates/CoordinateY' : [[0], [1], [2], [1], [4], [1], [2], [1], [6], [1]]} - # Nb : Low level load/write are tested in other file - tree = parse_yaml_cgns.to_cgns_tree(yt) - with TU.collective_tmp_dir(comm) as tmpdir: - filename = str(Path(tmpdir) / 'out.hdf') - IOH.write_partial(filename, tree, hdf_filter, comm) - comm.barrier() - - if comm.rank == 0: - tree = IOH.read_full(filename) - ytfull = """ - Base CGNSBase_t [2,2]: - ZoneU Zone_t [[6, 0, 0]]: - ZoneType ZoneType_t "Unstructured": - GridCoordinates GridCoordinates_t: - CoordinateX DataArray_t R8 [1,2,3,4,5,6]: - CoordinateY DataArray_t R8 [-1,-2,-3,-4,-5,-6]: - """ - assert PT.is_same_tree(tree, parse_yaml_cgns.to_cgns_tree(ytfull)) diff --git a/maia/io/test/test_meshb_io.py b/maia/io/test/test_meshb_io.py deleted file mode 100644 index f3100ef9..00000000 --- a/maia/io/test/test_meshb_io.py +++ /dev/null @@ -1,122 +0,0 @@ -import pytest -import pytest_parallel -import numpy as np -import mpi4py.MPI as MPI - -import maia -import maia.pytree as PT - -import maia.utils.test_utils as TU - -from maia.io import meshb_converter - -def test_get_tree_info(): - - dist_tree = maia.factory.generate_dist_block(11, 'TETRA_4', MPI.COMM_SELF) - zone = PT.get_all_Zone_t(dist_tree)[0] - - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - n_vtx = vtx_distri[1] - vtx_distri[0] - fields = {"Zeros": np.zeros(n_vtx), "Range": np.arange(n_vtx, dtype=float)} - PT.new_FlowSolution('FlowSolution', loc='Vertex', fields=fields, parent=zone) - - # Families are required - for ibc, bc in enumerate(PT.get_nodes_from_label(zone, 'BC_t')): - PT.new_child(bc, 'FamilyName', 'FamilyName_t', f'fam{ibc+1}') - - tree_info = meshb_converter.get_tree_info(dist_tree, ['FlowSolution']) - - assert len(tree_info) == 2 - assert tree_info['field_names'] == {'FlowSolution' : ['Zeros', 'Range']} - assert tree_info['bc_names'] == { - 'EdgeCenter': [], - 'FaceCenter': ['Zmin', 'Zmax', 'Xmin', 'Xmax', 'Ymin', 'Ymax'], - 'CellCenter': [], - } - -def test_cgns_to_meshb(tmp_path): - - # CGNS to mesh b is serial - dist_tree = maia.factory.generate_dist_block(11, 'TETRA_4', MPI.COMM_SELF) - zone = PT.get_all_Zone_t(dist_tree)[0] - - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - n_vtx = vtx_distri[1] - vtx_distri[0] - fields = {"Zeros": np.zeros(n_vtx), "Range": np.arange(n_vtx, dtype=float)} - PT.new_FlowSolution('FlowSolution', loc='Vertex', fields=fields, parent=zone) - PT.new_FlowSolution('Metric', loc='Vertex', fields={"Ones": np.ones(n_vtx)}, parent=zone) - - files = {'mesh': tmp_path / 'mesh.mesh', - 'sol' : tmp_path / 'metric.sol', - 'fld' : tmp_path / 'field.sol'} - - meshb_converter.cgns_to_meshb(dist_tree, files, [PT.get_node_from_name(zone, 'Ones')], ['FlowSolution'], constraints=None) - - # Check .mesh - with open(files['mesh']) as f: - lines = f.readlines() - - assert int(lines[lines.index('Vertices\n')+1]) == 1331 - assert int(lines[lines.index('Edges\n')+1]) == 0 - assert int(lines[lines.index('Triangles\n')+1]) == 1200 - assert int(lines[lines.index('Tetrahedra\n')+1]) == 5000 - assert lines[-1] == "End\n" - - st_triangles = lines.index('Triangles\n') - bc_tag = [int(l.split()[-1]) for l in lines[st_triangles+2:st_triangles+2+1200]] - u_tag, counts = np.unique(bc_tag, return_counts=True) - assert (u_tag == [1,2,3,4,5,6]).all() - assert (counts == 200).all() - - # Check .sol - with open(files['fld']) as f: - lines = f.readlines() - assert int(lines[lines.index('SolAtVertices\n')+1]) == 1331 - assert lines[lines.index('SolAtVertices\n')+2] == "2 1 1 \n" - - with open(files['sol']) as f: - lines = f.readlines() - assert int(lines[lines.index('SolAtVertices\n')+1]) == 1331 - assert lines[lines.index('SolAtVertices\n')+2] == "1 1 \n" - - -@pytest_parallel.mark.parallel(2) -def test_meshb_to_cgns(comm): - # Prepare test : write files in serial - tmp_dir = TU.create_collective_tmp_dir(comm) - files = {'mesh': tmp_dir / 'mesh.mesh', - 'fld' : tmp_dir / 'field.sol'} - - if comm.Get_rank() == 0: - dist_tree = maia.factory.generate_dist_block(11, 'TETRA_4', MPI.COMM_SELF) - zone = PT.get_all_Zone_t(dist_tree)[0] - - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - n_vtx = vtx_distri[1] - vtx_distri[0] - fields = {"Zeros": np.zeros(n_vtx), "Range": np.arange(n_vtx, dtype=float)} - PT.new_FlowSolution('FlowSolution', loc='Vertex', fields=fields, parent=zone) - - meshb_converter.cgns_to_meshb(dist_tree, files, [], ['FlowSolution'], constraints=None) - - tree_info = { - 'bc_names': { - 'EdgeCenter' : [], - 'FaceCenter' : ['bc1', 'bc2', 'bc3', 'bc4', 'bc5', 'bc6'], - 'CellCenter' : [], - }, - 'field_names' : { 'FlowSolution' : ['Zeros', 'Range'] }, - } - - comm.barrier() - dist_tree = meshb_converter.meshb_to_cgns(files, tree_info, comm) - - zone = PT.get_all_Zone_t(dist_tree)[0] - assert PT.Zone.n_vtx(zone) == 1331 and PT.Zone.n_cell(zone) == 5000 - - vtx_distri = PT.maia.getDistribution(zone, 'Vertex')[1] - sol = PT.get_node_from_path(zone, 'FlowSolution/Range')[1] - assert (sol == np.arange(vtx_distri[0], vtx_distri[1])).all() - - # TODO BCs are poorly distributed - bc = PT.get_node_from_name(zone, 'bc3') - assert PT.maia.getDistribution(bc, 'Index')[1][2] == 200 diff --git a/maia/io/test/test_part_tree.py b/maia/io/test/test_part_tree.py deleted file mode 100644 index c7312159..00000000 --- a/maia/io/test/test_part_tree.py +++ /dev/null @@ -1,104 +0,0 @@ -import pytest -import pytest_parallel -import mpi4py.MPI as MPI -import numpy as np -from pathlib import Path - -import maia -import maia.pytree as PT -import maia.pytree.maia as MT - -import maia.utils.test_utils as TU -from maia.pytree.yaml import parse_yaml_cgns -from maia.utils.parallel import utils as par_utils - -from maia.io import part_tree as PIO - -dtype = 'I8' if maia.npy_pdm_gnum_dtype == np.int64 else 'I4' - -class LogCapture: - def __init__(self): - self.msg = '' - def log(self, msg): - self.msg = self.msg + msg - - -@pytest_parallel.mark.parallel(4) -@pytest.mark.parametrize('single_file', [False, True]) -def test_write_part_tree(mpi_tmpdir, single_file, comm): - dtree = maia.factory.generate_dist_block(4, 'Poly', comm) - tree = maia.factory.partition_dist_tree(dtree, comm) - - expected_n_files = 1 + comm.Get_size() * int(not single_file) - - filename = Path(mpi_tmpdir) / 'out.hdf' - PIO.save_part_tree(tree, str(filename), comm, single_file) - comm.barrier() - assert filename.exists() - assert len(list(Path(mpi_tmpdir).glob('*'))) == expected_n_files - - if comm.Get_rank() == 0: - tree = maia.io.read_tree(str(filename)) - for rank in range(comm.Get_size()): - assert PT.get_node_from_path(tree, f'Base/zone.P{rank}.N0') is not None - assert PT.get_value(PT.get_node_from_path(tree, 'Base/zone.P1.N0/ZoneType')) == 'Unstructured' - - # Parallelism dependant ... - # ref = parse_yaml_cgns.to_node(f""" - # Xmax BC_t "Null": - # GridLocation GridLocation_t "FaceCenter": - # PointList IndexArray_t [[19,20,21,22,23,24]]: - # :CGNS#GlobalNumbering UserDefinedData_t: - # Index DataArray_t {dtype} [2,3,4,5,6,7]: - # """) - # assert PT.is_same_tree(PT.get_node_from_path(tree, 'Base/zone.P1.N0/ZoneBC/Xmax'), ref) - -@pytest_parallel.mark.parallel(4) -@pytest.mark.parametrize('single_file', [False, True]) -def test_read_part_tree(mpi_tmpdir, single_file, comm): - - # Prepare test (produce part_tree_file) - dtree = maia.factory.generate_dist_block(4, 'Poly', comm) - tree = maia.factory.partition_dist_tree(dtree, comm) - expected = np.copy(PT.get_node_from_name(tree, 'CoordinateX')[1]) #Backup array for later check - filename = Path(mpi_tmpdir) / 'out.hdf' - PIO.save_part_tree(tree, str(filename), comm, single_file) - comm.barrier() - - # Actual test - tree = PIO.read_part_tree(str(filename), comm) - zones = PT.get_all_Zone_t(tree) - - # Check: data should have been loaded - assert len(zones) == 1 - assert PT.get_name(zones[0]) == f'zone.P{comm.Get_rank()}.N0' - assert PT.get_node_from_name(zones[0], 'CoordinateX')[1].size > 0 - assert (PT.get_node_from_name(zones[0], 'CoordinateX')[1] == expected).all() - - -@pytest_parallel.mark.parallel(2) -@pytest.mark.parametrize('redispatch', [False, True]) -def test_read_part_tree_redispatch(mpi_tmpdir, redispatch, comm): - - # To get logs in printer.msg - err_printer = LogCapture() - war_printer = LogCapture() - from maia.utils.logging import add_printer_to_logger - add_printer_to_logger('maia-errors', err_printer) - add_printer_to_logger('maia-warnings', war_printer) - - dtree = maia.factory.generate_dist_block(4, 'Poly', comm) - tree = maia.factory.partition_dist_tree(dtree, comm) - - filename = Path(mpi_tmpdir) / 'out.hdf' - PIO.save_part_tree(tree, str(filename), comm) - comm.barrier() - if comm.Get_rank() == 0: - tree = PIO.read_part_tree(str(filename), MPI.COMM_SELF, redispatch=redispatch) - if redispatch: - assert len(PT.get_all_Zone_t(tree)) == 2 - assert PT.get_name(PT.get_all_Zone_t(tree)[1]) == 'zone.P0.N1' - assert 'written for 2 procs' in war_printer.msg - else: - assert len(PT.get_all_Zone_t(tree)) == 1 - assert 'written for 2 procs' in err_printer.msg diff --git a/maia/pytest.ini b/maia/pytest.ini deleted file mode 100644 index 6fed4f65..00000000 --- a/maia/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -# Erase default pytest setting, who stip "dist" directory https://github.com/pytest-dev/pytest/issues/1544 -norecursedirs = ['.*', 'CVS', '_darcs', '{arch}', '*.egg'] diff --git a/maia/pytree/__init__.py b/maia/pytree/__init__.py deleted file mode 100644 index f896e42b..00000000 --- a/maia/pytree/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .sids import * -from .walk import * -from .node import * - -from .compare import * -from .path_utils import * - -import maia.pytree.utils as utils \ No newline at end of file diff --git a/maia/pytree/_unused.py b/maia/pytree/_unused.py deleted file mode 100644 index 8214361d..00000000 --- a/maia/pytree/_unused.py +++ /dev/null @@ -1,25 +0,0 @@ -# -------------------------------------------------------------------------- -def getParentFromPredicate(start, node, predicate, prev=None): - """Return thee first parent node matching type.""" - if id(start) == id(node): - return prev - if predicate(start): - prev = start - for n in start[2]: - ret = getParentFromPredicate(n, node, parentType, prev) - if ret is not None: return ret - return None - -def getParentsFromPredicate(start, node, predicate, l=[]): - """Return all parent nodes matching type.""" - if id(start) == id(node): - return l - if predicate(start): - l.append(start) - for n in start[2]: - ret = getParentsFromPredicate(n, node, predicate, l) - if ret != []: return ret - return [] - -# -------------------------------------------------------------------------- - diff --git a/maia/pytree/algo_utils.py b/maia/pytree/algo_utils.py deleted file mode 100644 index 3c4a7ded..00000000 --- a/maia/pytree/algo_utils.py +++ /dev/null @@ -1,85 +0,0 @@ -import operator - -def find(seq, pred_or_value): - if callable(pred_or_value): - pred = pred_or_value - else: - value = pred_or_value - pred = lambda x: x == value - - i = 0 - while i -#include "std_e/utils/enum.hpp" - -namespace CGNS { - -//Enum section -//############ - -//The strings defined below are type names used for node labels -//############################################################# - -// Types as strings -// ----------------- -STD_E_ENUM(Label, - CGNSTree_t, - CGNSBase_t, - Zone_t, - ZoneType_t, - GridCoordinates_t, - GridLocation_t, - ZoneBC_t, - BC_t, - BCData_t, - BCDataSet_t, - ZoneGridConnectivity_t, - GridConnectivity1to1_t, - GridConnectivity_t, - Family_t, - FamilyName_t, - AdditionalFamilyName_t, - AdditionalExponents_t, - AdditionalUnits_t, - ArbitraryGridMotion_t, - Area_t, - AverageInterface_t, - Axisymmetry_t, - BCProperty_t, - BCTypeSimple_t, - BCTypeCompound_ts, - BaseIterativeData_t, - CGNSLibraryVersion_t, - ChemicalKineticsModel_t, - ConvergenceHistory_t, - DataArray_t, - DataClass_t, - DataConversion_t, - Descriptor_t, - DimensionalExponents_t, - DimensionalUnits_t, - DiscreteData_t, - Elements_t, - FamilyBC_t, - FamilyBCDataSet_t, - FlowEquationSet_t, - FlowSolution_t, - GasModel_t, - GasModelType_t, - GeometryEntity_t, - GeometryFile_t, - GeometryFormat_t, - GeometryReference_t, - GoverningEquations_t, - Gravity_t, - GridConnectivityProperty_t, - GridConnectivityType_t, - IndexArray_t, - IndexRange_t, - IntegralData_t, - InwardNormalList_t, - Ordinal_t, - OversetHoles_t, - Periodic_t, - ReferenceState_t, - RigidGridMotion_t, - Rind_t, - RotatingCoordinates_t, - SimulationType_t, - ThermalConductivityModel_t, - ThermalRelaxationModel_t, - TurbulenceClosure_t, - TurbulenceModel_t, - UserDefinedData_t, - ViscosityModel_t, - ViscosityModelType_t, - WallFunction_t, - ZoneIterativeData_t, - ZoneSubRegion_t, - UserDefined_t, - BulkRegionFamily_t, - BndConditionFamily_t, - BndConnectionFamily_t, - Invalid_t -); - -constexpr int nb_cgns_labels = std_e::enum_size