├── examples ├── CMakeLists.txt ├── dem │ ├── CMakeLists.txt │ ├── inputs │ │ └── powder_fill.json │ └── powder_fill.cpp ├── mechanics │ ├── inputs │ │ ├── elastic_wave.json │ │ ├── crack_branching.json │ │ ├── kalthoff_winkler.json │ │ ├── plate_with_hole.json │ │ ├── random_cracks.json │ │ ├── crack_inclusion.json │ │ ├── dogbone_tensile_test.json │ │ └── fragmenting_cylinder.json │ ├── CMakeLists.txt │ ├── elastic_wave.cpp │ ├── kalthoff_winkler.cpp │ ├── crack_branching.cpp │ ├── plate_with_hole.cpp │ ├── crack_inclusion.cpp │ └── fragmenting_cylinder.cpp └── thermomechanics │ ├── CMakeLists.txt │ ├── inputs │ ├── thermal_deformation.json │ ├── thermal_deformation_heat_transfer.json │ ├── heat_transfer.json │ └── thermal_crack.json │ ├── thermal_deformation.cpp │ ├── thermal_deformation_heat_transfer.cpp │ └── thermal_crack.cpp ├── .gitignore ├── .clang-format ├── .github └── workflows │ ├── format-check.yml │ └── Weekly.yml ├── cmake ├── CabanaPD_Config.cmakein └── FindCLANG_FORMAT.cmake ├── .pre-commit-config.yaml ├── unit_test ├── inputs │ ├── hertzian_contact.json │ └── hertzian_jkr_contact.json ├── tstNormalRepulsion.hpp ├── TestCUDA_Category.hpp ├── TestOPENMP_Category.hpp ├── TestSERIAL_Category.hpp ├── TestPTHREAD_Category.hpp ├── TestHIP_Category.hpp ├── mpi_unit_test_main.cpp ├── CMakeLists.txt ├── tstIntegrator.hpp ├── tstHertz.hpp ├── tstHertzJKR.hpp └── tstComm.hpp ├── CONTRIBUTING.md ├── src ├── CabanaPD_config.hpp.cmakein ├── CabanaPD_Constants.hpp ├── CMakeLists.txt ├── CabanaPD_Fields.hpp ├── CabanaPD.hpp ├── CabanaPD_Timer.hpp ├── CabanaPD_BodyTerm.hpp ├── force_models │ ├── CabanaPD_Contact.hpp │ ├── CabanaPD_Hertzian.hpp │ └── CabanaPD_HertzianJKR.hpp ├── CabanaPD_Types.hpp ├── CabanaPD_ForceModelsMulti.hpp ├── force │ └── CabanaPD_Contact.hpp ├── CabanaPD_OutputProfiles.hpp ├── CabanaPD_HeatTransfer.hpp └── CabanaPD_Boundary.hpp ├── LICENSE ├── CHANGELOG.md └── CMakeLists.txt /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(mechanics) 2 | add_subdirectory(thermomechanics) 3 | add_subdirectory(dem) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | build/ 4 | 5 | # Output Files 6 | *.out 7 | *.silo 8 | *.h5 9 | *.xmf 10 | *.err 11 | *.in.json 12 | -------------------------------------------------------------------------------- /examples/dem/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(PowderFill powder_fill.cpp) 2 | target_link_libraries(PowderFill LINK_PUBLIC CabanaPD) 3 | install(TARGETS PowderFill DESTINATION ${CMAKE_INSTALL_BINDIR}) 4 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | --- 3 | Language: Cpp 4 | AlwaysBreakTemplateDeclarations: true 5 | BreakBeforeBraces: Allman 6 | BinPackParameters: true 7 | IndentWidth: 4 8 | SpacesInParentheses: true 9 | BreakConstructorInitializersBeforeComma: true 10 | PointerAlignment: Left 11 | AlwaysBreakAfterReturnType: None 12 | KeepEmptyLinesAtTheStartOfBlocks: false 13 | -------------------------------------------------------------------------------- /.github/workflows/format-check.yml: -------------------------------------------------------------------------------- 1 | name: Clang-Format Check 2 | on: [pull_request] 3 | jobs: 4 | formatting-check: 5 | name: Formatting Check 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - name: Run clang-format style check 10 | uses: jidicula/clang-format-action@v4.6.2 11 | with: 12 | clang-format-version: '14' 13 | -------------------------------------------------------------------------------- /cmake/CabanaPD_Config.cmakein: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}" ) 3 | list(APPEND CMAKE_PREFIX_PATH @CMAKE_PREFIX_PATH@) 4 | find_dependency(Cabana REQUIRED COMPONENTS Core Grid) 5 | if(NOT CabanaPD_INTERNAL_JSON) 6 | find_dependency(nlohmann_json REQUIRED) 7 | endif() 8 | include("${CMAKE_CURRENT_LIST_DIR}/CabanaPD_Targets.cmake") 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.4.0 4 | hooks: 5 | - id: check-merge-conflict 6 | - id: end-of-file-fixer 7 | 8 | - repo: https://github.com/pre-commit/mirrors-clang-format 9 | rev: v14.0.0 10 | hooks: 11 | - id: clang-format 12 | types_or: [c++] 13 | args: ["-style=file", "-i"] 14 | 15 | - repo: https://github.com/codespell-project/codespell 16 | rev: v2.2.6 17 | hooks: 18 | - id: codespell 19 | args: ["-L", "gage"] 20 | -------------------------------------------------------------------------------- /examples/mechanics/inputs/elastic_wave.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [41, 41, 41]}, 3 | "system_size" : {"value": [1.0, 1.0, 1.0], "unit": ""}, 4 | "density" : {"value": 100.0, "unit": ""}, 5 | "bulk_modulus" : {"value": 1.0, "unit": ""}, 6 | "shear_modulus" : {"value": 0.5, "unit": ""}, 7 | "horizon" : {"value": 0.075, "unit": ""}, 8 | "final_time" : {"value": 0.6, "unit": ""}, 9 | "timestep" : {"value": 0.01, "unit": ""}, 10 | "timestep_safety_factor" : {"value": 0.85}, 11 | "output_frequency" : {"value": 5}, 12 | "output_reference" : {"value": true} 13 | } 14 | -------------------------------------------------------------------------------- /examples/mechanics/inputs/crack_branching.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [400, 160, 8]}, 3 | "system_size" : {"value": [0.1, 0.04, 0.002], "unit": "m"}, 4 | "density" : {"value": 2440, "unit": "kg/m^3"}, 5 | "elastic_modulus" : {"value": 72e+9, "unit": "Pa"}, 6 | "fracture_energy" : {"value": 3.8, "unit": "J/m^2"}, 7 | "horizon" : {"value": 0.001, "unit": "m"}, 8 | "traction" : {"value": 2e6, "unit": "Pa"}, 9 | "final_time" : {"value": 43e-6, "unit": "s"}, 10 | "timestep" : {"value": 4.5e-8, "unit": "s"}, 11 | "timestep_safety_factor" : {"value": 0.85}, 12 | "output_frequency" : {"value": 5}, 13 | "output_reference" : {"value": true} 14 | } 15 | -------------------------------------------------------------------------------- /examples/mechanics/inputs/kalthoff_winkler.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [151, 301, 14]}, 3 | "system_size" : {"value": [0.1, 0.2, 0.009], "unit": "m"}, 4 | "density" : {"value": 8000, "unit": "kg/m^3"}, 5 | "elastic_modulus" : {"value": 191e+9, "unit": "Pa"}, 6 | "fracture_energy" : {"value": 42408, "unit": "J/m^2"}, 7 | "horizon" : {"value": 0.002, "unit": "m"}, 8 | "impactor_velocity" : {"value": 16, "unit": "m/s"}, 9 | "final_time" : {"value": 70e-6, "unit": "s"}, 10 | "timestep" : {"value": 1e-7, "unit": "s"}, 11 | "timestep_safety_factor" : {"value": 0.85}, 12 | "output_frequency" : {"value": 10}, 13 | "output_reference" : {"value": true} 14 | } 15 | -------------------------------------------------------------------------------- /examples/thermomechanics/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ThermalDeformation thermal_deformation.cpp) 2 | target_link_libraries(ThermalDeformation LINK_PUBLIC CabanaPD) 3 | 4 | add_executable(ThermalCrack thermal_crack.cpp) 5 | target_link_libraries(ThermalCrack LINK_PUBLIC CabanaPD) 6 | 7 | add_executable(ThermalDeformationHeatTransfer thermal_deformation_heat_transfer.cpp) 8 | target_link_libraries(ThermalDeformationHeatTransfer LINK_PUBLIC CabanaPD) 9 | 10 | add_executable(ThermalDeformationHeatTransferPrenotched thermal_deformation_heat_transfer_prenotched.cpp) 11 | target_link_libraries(ThermalDeformationHeatTransferPrenotched LINK_PUBLIC CabanaPD) 12 | 13 | install(TARGETS ThermalDeformation ThermalCrack ThermalDeformationHeatTransfer ThermalDeformationHeatTransferPrenotched DESTINATION ${CMAKE_INSTALL_BINDIR}) 14 | -------------------------------------------------------------------------------- /examples/mechanics/inputs/plate_with_hole.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [200, 200, 4]}, 3 | "system_size" : {"value": [0.050, 0.050, 0.001], "unit": "m"}, 4 | "density" : {"value": 8000, "unit": "kg/m^3"}, 5 | "elastic_modulus" : {"value": 191e+9, "unit": "Pa"}, 6 | "fracture_energy" : {"value": 42408, "unit": "J/m^2"}, 7 | "horizon" : {"value": 0.001, "unit": "m"}, 8 | "hole_radius" : {"value": 0.0025, "unit": "m"}, 9 | "traction" : {"value": 10e6, "unit": "Pa"}, 10 | "final_time" : {"value": 43e-6, "unit": "s"}, 11 | "timestep" : {"value": 5e-8, "unit": "s"}, 12 | "timestep_safety_factor" : {"value": 0.85}, 13 | "output_frequency" : {"value": 5}, 14 | "output_reference" : {"value": true} 15 | } 16 | -------------------------------------------------------------------------------- /unit_test/inputs/hertzian_contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [1, 1, 1]}, 3 | "system_size" : {"value": [1.0e-3, 1.0e-3, 1.0e-3], "unit": "m"}, 4 | "density" : {"value": 7.95e3, "unit": "kg/m^3"}, 5 | "volume" : {"value": 5.236e-13, "unit": "m^3"}, 6 | "elastic_modulus" : {"value": 195.6e9, "unit": "Pa"}, 7 | "poisson_ratio" : {"value": 0.25, "unit": ""}, 8 | "restitution" : {"value": 0.5}, 9 | "radius" : {"value": 5e-5 }, 10 | "radius_extend" : {"value": 1e-8 }, 11 | "final_time" : {"value": 1e-5, "unit": "s"}, 12 | "timestep" : {"value": 0.8e-8, "unit": "s"}, 13 | "timestep_safety_factor" : {"value": 0.8}, 14 | "output_frequency" : {"value": 10000}, 15 | "output_reference" : {"value": false} 16 | } 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you'd like to contribute to CabanaPD, please open a [pull 4 | request](https://help.github.com/articles/using-pull-requests/) with 5 | `main` as the destination branch on the 6 | [repository](https://github.com/ORNL/CabanaPD) and allow edits from 7 | maintainers in the pull request. 8 | 9 | Your pull request must pass tests, which includes using the coding 10 | style from `.clang-format` (enforced with clang-format-14), and be 11 | reviewed by at least one CabanaPD developer. Formatting can be applied 12 | with `make format` within the build folder. 13 | 14 | `pre-commit` is a useful tool for ensuring feature branches are ready for 15 | review by running automatic checks locally before a commit is made. 16 | [Installation details](https://pre-commit.com/#install) (once per system) and 17 | [activation details](https://pre-commit.com/#usage) (once per repo) are 18 | available. 19 | -------------------------------------------------------------------------------- /examples/mechanics/inputs/random_cracks.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [400, 160, 8]}, 3 | "system_size" : {"value": [0.1, 0.04, 0.002], "unit": "m"}, 4 | "density" : {"value": 2440, "unit": "kg/m^3"}, 5 | "elastic_modulus" : {"value": 72e+9, "unit": "Pa"}, 6 | "fracture_energy" : {"value": 3.8, "unit": "J/m^2"}, 7 | "horizon" : {"value": 0.001, "unit": "m"}, 8 | "traction" : {"value": 2e6, "unit": "Pa"}, 9 | "minimum_prenotch_length" : {"value": 0.001, "unit": "m"}, 10 | "maximum_prenotch_length" : {"value": 0.0025, "unit": "m"}, 11 | "final_time" : {"value": 43e-6, "unit": "s"}, 12 | "timestep" : {"value": 4.5e-8, "unit": "s"}, 13 | "timestep_safety_factor" : {"value": 0.85}, 14 | "output_frequency" : {"value": 5}, 15 | "output_reference" : {"value": true} 16 | } 17 | -------------------------------------------------------------------------------- /examples/thermomechanics/inputs/thermal_deformation.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [101, 31, 3]}, 3 | "system_size" : {"value": [1.0, 0.3, 0.03], "unit": "m"}, 4 | "density" : {"value": 3980, "unit": "kg/m^3"}, 5 | "elastic_modulus" : {"value": 370e+9, "unit": "Pa"}, 6 | "thermal_expansion_coeff" : {"value": 7.5E-6, "unit": "oC^{-1}"}, 7 | "thermal_conductivity" : {"value": 31, "unit": "W/(m.K)"}, 8 | "specific_heat_capacity" : {"value": 880, "unit": "J/(kg.K)"}, 9 | "reference_temperature" : {"value": 20.0, "unit": "oC"}, 10 | "horizon" : {"value": 0.03, "unit": "m"}, 11 | "final_time" : {"value": 0.01, "unit": "s"}, 12 | "timestep" : {"value": 7.5E-7, "unit": "s"}, 13 | "thermal_subcycle_steps": {"value": 100}, 14 | "output_frequency" : {"value": 100}, 15 | "output_reference" : {"value": true} 16 | } 17 | -------------------------------------------------------------------------------- /unit_test/inputs/hertzian_jkr_contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [1, 1, 1]}, 3 | "system_size" : {"value": [1.0e-3, 1.0e-3, 1.0e-3], "unit": "m"}, 4 | "density" : {"value": 7.95e3, "unit": "kg/m^3"}, 5 | "volume" : {"value": 5.236e-13, "unit": "m^3"}, 6 | "elastic_modulus" : {"value": 195.6e9, "unit": "Pa"}, 7 | "poisson_ratio" : {"value": 0.25, "unit": ""}, 8 | "restitution" : {"value": 1.0}, 9 | "surface_adhesion" : {"value": 10, "unit": "J/m^2"}, 10 | "radius" : {"value": 5e-5 }, 11 | "radius_extend" : {"value": 1e-8 }, 12 | "final_time" : {"value": 5e-7, "unit": "s"}, 13 | "timestep" : {"value": 1e-9, "unit": "s"}, 14 | "timestep_safety_factor" : {"value": 0.8}, 15 | "output_frequency" : {"value": 500}, 16 | "output_reference" : {"value": false} 17 | } 18 | -------------------------------------------------------------------------------- /examples/mechanics/inputs/crack_inclusion.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [400, 160, 8]}, 3 | "system_size" : {"value": [0.1, 0.04, 0.002], "unit": "m"}, 4 | "inclusion_center" : {"value": [0.025, 0.0], "unit": "m"}, 5 | "inclusion_radius" : {"value": 0.01, "unit": "m"}, 6 | "density" : {"value": [2440, 2440], "unit": "kg/m^3"}, 7 | "elastic_modulus" : {"value": [72e+9, 144e+9], "unit": "Pa"}, 8 | "Poisson's_ratio" : {"value": [0.22, 0.22]}, 9 | "fracture_energy" : {"value": [3.8, 38.0], "unit": "J/m^2"}, 10 | "horizon" : {"value": 0.001, "unit": "m"}, 11 | "traction" : {"value": 2e6, "unit": "Pa"}, 12 | "final_time" : {"value": 43e-6, "unit": "s"}, 13 | "timestep" : {"value": 4.5e-8, "unit": "s"}, 14 | "timestep_safety_factor" : {"value": 0.85}, 15 | "output_frequency" : {"value": 5}, 16 | "output_reference" : {"value": true} 17 | } 18 | -------------------------------------------------------------------------------- /src/CabanaPD_config.hpp.cmakein: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef CabanaPD_CONFIG_HPP 13 | #define CabanaPD_CONFIG_HPP 14 | 15 | #define CabanaPD_VERSION_STRING "@PROJECT_VERSION@" 16 | #define CabanaPD_GIT_COMMIT_HASH "@CabanaPD_GIT_COMMIT_HASH@" 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /examples/thermomechanics/inputs/thermal_deformation_heat_transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [49, 49, 49]}, 3 | "system_size" : {"value": [0.1, 0.1, 0.1], "unit": "m"}, 4 | "density" : {"value": 8915, "unit": "kg/m^3"}, 5 | "elastic_modulus" : {"value": 115e+9, "unit": "Pa"}, 6 | "fracture_energy" : {"value": 1.3913e+04, "unit": "J/m^2"}, 7 | "thermal_expansion_coeff" : {"value": 17e-6, "unit": "oC^{-1}"}, 8 | "thermal_conductivity" : {"value": 387, "unit": "W/(m.K)"}, 9 | "specific_heat_capacity" : {"value": 385, "unit": "J/(kg.K)"}, 10 | "reference_temperature" : {"value": 100.0, "unit": "oC"}, 11 | "horizon" : {"value": 0.00615, "unit": "m"}, 12 | "final_time" : {"value": 8, "unit": "s"}, 13 | "timestep" : {"value": 4.2e-07, "unit": "s"}, 14 | "thermal_subcycle_steps" : {"value": 4.5e+4}, 15 | "output_frequency" : {"value": 10}, 16 | "output_reference" : {"value": true} 17 | } 18 | -------------------------------------------------------------------------------- /src/CabanaPD_Constants.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef CONSTANTS_H 13 | #define CONSTANTS_H 14 | 15 | #include "Kokkos_Core.hpp" 16 | 17 | namespace CabanaPD 18 | { 19 | 20 | constexpr double pi = Kokkos::numbers::pi_v; 21 | 22 | } // namespace CabanaPD 23 | #endif 24 | -------------------------------------------------------------------------------- /examples/dem/inputs/powder_fill.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [50, 50, 25]}, 3 | "system_size" : {"value": [0.25, 0.25, 0.125], "unit": "m"}, 4 | "density" : {"value": 2.0e3, "unit": "kg/m^3"}, 5 | "volume" : {"value": 6.544985e-8, "unit": "m^3"}, 6 | "elastic_modulus" : {"value": 1.0e5, "unit": "Pa"}, 7 | "poisson_ratio" : {"value": 0.25, "unit": ""}, 8 | "restitution" : {"value": 0.5}, 9 | "radius" : {"value": 2.5e-3 }, 10 | "radius_extend" : {"value": 2.5e-3 }, 11 | "cylinder_diameter" : {"value": 0.25, "unit": "m"}, 12 | "wall_thickness" : {"value": 0.0045, "unit": "m"}, 13 | "min_height" : {"value": 0.01, "unit": "m"}, 14 | "final_time" : {"value": 0.5, "unit": "s"}, 15 | "timestep" : {"value": 2e-6, "unit": "s"}, 16 | "timestep_safety_factor" : {"value": 0.9}, 17 | "output_frequency" : {"value": 1000}, 18 | "output_reference" : {"value": false} 19 | } 20 | -------------------------------------------------------------------------------- /examples/mechanics/inputs/dogbone_tensile_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [251, 29, 5]}, 3 | "system_size" : {"value": [0.165, 0.019, 0.0032], "unit": "m"}, 4 | "gage_length" : {"value": 0.050, "unit": "m"}, 5 | "width_narrow_section" : {"value": 0.013, "unit": "m"}, 6 | "fillet_radius" : {"value": 0.076, "unit": "m"}, 7 | "distance_between_grips" : {"value": 0.115, "unit": "m"}, 8 | "density" : {"value": 1200, "unit": "kg/m^3"}, 9 | "elastic_modulus" : {"value": 3e+9, "unit": "Pa"}, 10 | "fracture_energy" : {"value": 3000, "unit": "J/m^2"}, 11 | "horizon" : {"value": 0.002, "unit": "m"}, 12 | "yield_stress" : {"value": 30e6, "unit": "Pa"}, 13 | "grip_velocity" : {"value": 1e-1, "unit": "m/s"}, 14 | "final_time" : {"value": 1.5e-2, "unit": "s"}, 15 | "timestep" : {"value": 3.0e-7, "unit": "s"}, 16 | "timestep_safety_factor" : {"value": 0.85}, 17 | "output_frequency" : {"value": 200}, 18 | "output_reference" : {"value": false} 19 | } 20 | -------------------------------------------------------------------------------- /examples/thermomechanics/inputs/heat_transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [49, 49, 49]}, 3 | "system_size" : {"value": [0.1, 0.1, 0.1], "unit": "m"}, 4 | "density" : {"value": 8915, "unit": "kg/m^3"}, 5 | "elastic_modulus" : {"value": 115e+9, "unit": "Pa"}, 6 | "fracture_energy" : {"value": 1.3913e+04, "unit": "J/m^2"}, 7 | "thermal_expansion_coeff" : {"value": 0.0, "unit": "oC^{-1}"}, 8 | "thermal_conductivity" : {"value": 387, "unit": "W/(m.K)"}, 9 | "specific_heat_capacity" : {"value": 385, "unit": "J/(kg.K)"}, 10 | "reference_temperature" : {"value": 100.0, "unit": "oC"}, 11 | "horizon" : {"value": 0.00615, "unit": "m"}, 12 | "final_time" : {"value": 8, "unit": "s"}, 13 | "timestep" : {"value": 0.02, "unit": "s", "note": "This timestep is too large for mechanics to be stable. Use thermal_deformation_heat_transfer.json instead if coupled thermomechanics is desired."}, 14 | "thermal_subcycle_steps" : {"value": 1}, 15 | "output_frequency" : {"value": 10}, 16 | "output_reference" : {"value": true} 17 | } 18 | -------------------------------------------------------------------------------- /examples/mechanics/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ElasticWave elastic_wave.cpp) 2 | target_link_libraries(ElasticWave LINK_PUBLIC CabanaPD) 3 | 4 | add_executable(KalthoffWinkler kalthoff_winkler.cpp) 5 | target_link_libraries(KalthoffWinkler LINK_PUBLIC CabanaPD) 6 | 7 | add_executable(CrackBranching crack_branching.cpp) 8 | target_link_libraries(CrackBranching LINK_PUBLIC CabanaPD) 9 | 10 | add_executable(FragmentingCylinder fragmenting_cylinder.cpp) 11 | target_link_libraries(FragmentingCylinder LINK_PUBLIC CabanaPD) 12 | 13 | add_executable(RandomCracks random_cracks.cpp) 14 | target_link_libraries(RandomCracks LINK_PUBLIC CabanaPD) 15 | 16 | add_executable(DogboneTensileTest dogbone_tensile_test.cpp) 17 | target_link_libraries(DogboneTensileTest LINK_PUBLIC CabanaPD) 18 | 19 | add_executable(PlateWithHole plate_with_hole.cpp) 20 | target_link_libraries(PlateWithHole LINK_PUBLIC CabanaPD) 21 | 22 | add_executable(CrackInclusion crack_inclusion.cpp) 23 | target_link_libraries(CrackInclusion LINK_PUBLIC CabanaPD) 24 | 25 | install(TARGETS ElasticWave KalthoffWinkler CrackBranching FragmentingCylinder RandomCracks DogboneTensileTest PlateWithHole CrackInclusion DESTINATION ${CMAKE_INSTALL_BINDIR}) 26 | -------------------------------------------------------------------------------- /examples/thermomechanics/inputs/thermal_crack.json: -------------------------------------------------------------------------------- 1 | { 2 | "num_cells" : {"value": [501, 101, 11]}, 3 | "system_size" : {"value": [0.05, 0.01, 0.001], "unit": "m"}, 4 | "density" : {"value": 3980, "unit": "kg/m^3"}, 5 | "elastic_modulus" : {"value": 370e+9, "unit": "Pa"}, 6 | "fracture_energy" : {"value": 24.32, "unit": "J/m^2"}, 7 | "thermal_expansion_coeff" : {"value": 7.5E-6, "unit": "oC^{-1}"}, 8 | "thermal_conductivity" : {"value": 31, "unit": "W/(m.K)"}, 9 | "specific_heat_capacity" : {"value": 880, "unit": "J/(kg.K)"}, 10 | "reference_temperature" : {"value": 300.0, "unit": "oC"}, 11 | "background_temperature" : {"value": 20.0, "unit": "oC"}, 12 | "surface_temperature_ramp_time" : {"value": 0.001, "unit": "s"}, 13 | "horizon" : {"value": 3.0e-4, "unit": "m"}, 14 | "final_time" : {"value": 7.5e-4, "unit": "s"}, 15 | "timestep" : {"value": 7.5E-9, "unit": "s"}, 16 | "thermal_subcycle_steps" : {"value": 8e+4}, 17 | "output_frequency" : {"value": 500}, 18 | "output_reference" : {"value": true} 19 | } 20 | -------------------------------------------------------------------------------- /unit_test/tstNormalRepulsion.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace Test 18 | { 19 | 20 | // Test construction. 21 | TEST( TEST_CATEGORY, test_force_normal_construct ) 22 | { 23 | double delta = 10.0; 24 | double radius = 5.0; 25 | double extend = 1.0; 26 | double K = 2.0; 27 | CabanaPD::NormalRepulsionModel contact_model( delta, radius, extend, K ); 28 | } 29 | 30 | } // end namespace Test 31 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file(CabanaPD_config.hpp.cmakein CabanaPD_config.hpp @ONLY) 2 | 3 | file(GLOB HEADERS GLOB *.hpp) 4 | file(GLOB FORCE_HEADERS GLOB force/*.hpp) 5 | file(GLOB FORCE_MODEL_HEADERS GLOB force_models/*.hpp) 6 | 7 | install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 8 | install(FILES ${FORCE_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/force) 9 | install(FILES ${FORCE_MODEL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/force_models) 10 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CabanaPD_config.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 11 | 12 | add_library(CabanaPD INTERFACE) 13 | add_library(CabanaPD::CabanaPD ALIAS CabanaPD) 14 | 15 | target_include_directories(CabanaPD INTERFACE 16 | $ 17 | $ 18 | $) 19 | 20 | target_link_libraries(CabanaPD INTERFACE Cabana::Core Cabana::Grid nlohmann_json::nlohmann_json) 21 | 22 | install(TARGETS CabanaPD 23 | EXPORT CabanaPD_Targets 24 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 25 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 26 | 27 | install(EXPORT CabanaPD_Targets 28 | FILE CabanaPD_Targets.cmake 29 | NAMESPACE CabanaPD:: 30 | DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/CabanaPD) 31 | -------------------------------------------------------------------------------- /examples/mechanics/inputs/fragmenting_cylinder.json: -------------------------------------------------------------------------------- 1 | { 2 | "dx" : {"value": [0.001, 0.001, 0.001], "unit": "m"}, 3 | "system_size" : {"value": [0.2, 0.2, 0.3], "unit": "m"}, 4 | "density" : {"value": 7800, "unit": "kg/m^3"}, 5 | "bulk_modulus" : {"value": 130e+9, "unit": "Pa"}, 6 | "shear_modulus" : {"value": 78e+9, "unit": "Pa"}, 7 | "critical_stretch" : {"value": 0.02}, 8 | "horizon" : {"value": 0.00417462, "unit": "m"}, 9 | "use_contact" : {"value": true}, 10 | "contact_horizon_factor" : {"value": 0.9}, 11 | "contact_horizon_extend_factor" : {"value": 0.01}, 12 | "cylinder_outer_radius" : {"value": 0.025, "unit": "m"}, 13 | "cylinder_inner_radius" : {"value": 0.02, "unit": "m"}, 14 | "cylinder_height" : {"value": 0.1, "unit": "m"}, 15 | "max_radial_velocity" : {"value": 200, "unit": "m/s"}, 16 | "min_radial_velocity" : {"value": 50, "unit": "m/s"}, 17 | "max_vertical_velocity" : {"value": 100, "unit": "m/s"}, 18 | "final_time" : {"value": 2.4e-4, "unit": "s"}, 19 | "timestep" : {"value": 1.7e-07, "unit": "s"}, 20 | "timestep_safety_factor" : {"value": 0.70}, 21 | "output_frequency" : {"value": 50}, 22 | "output_reference" : {"value": false} 23 | } 24 | -------------------------------------------------------------------------------- /src/CabanaPD_Fields.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef FIELDS_HPP 13 | #define FIELDS_HPP 14 | 15 | #include 16 | 17 | namespace CabanaPD 18 | { 19 | //---------------------------------------------------------------------------// 20 | // Fields. 21 | //---------------------------------------------------------------------------// 22 | namespace Field 23 | { 24 | 25 | struct ReferencePosition : Cabana::Field::Position<3> 26 | { 27 | static std::string label() { return "reference_positions"; } 28 | }; 29 | 30 | struct Force : Cabana::Field::Vector 31 | { 32 | static std::string label() { return "forces"; } 33 | }; 34 | 35 | struct NoFail : Cabana::Field::Scalar 36 | { 37 | static std::string label() { return "no_fail"; } 38 | }; 39 | 40 | } // namespace Field 41 | } // namespace CabanaPD 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022 by Oak Ridge National Laboratory 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /unit_test/TestCUDA_Category.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | /**************************************************************************** 13 | * Copyright (c) 2018-2021 by the Cabana authors * 14 | * All rights reserved. * 15 | * * 16 | * This file is part of the Cabana library. Cabana is distributed under a * 17 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 18 | * the top-level directory. * 19 | * * 20 | * SPDX-License-Identifier: BSD-3-Clause * 21 | ****************************************************************************/ 22 | 23 | #ifndef TEST_CUDA_CATEGORY_HPP 24 | #define TEST_CUDA_CATEGORY_HPP 25 | 26 | #define TEST_CATEGORY cuda 27 | #define TEST_EXECSPACE Kokkos::Cuda 28 | #define TEST_MEMSPACE Kokkos::CudaSpace 29 | #define TEST_DEVICE Kokkos::Device 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /unit_test/TestOPENMP_Category.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | /**************************************************************************** 13 | * Copyright (c) 2018-2021 by the Cabana authors * 14 | * All rights reserved. * 15 | * * 16 | * This file is part of the Cabana library. Cabana is distributed under a * 17 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 18 | * the top-level directory. * 19 | * * 20 | * SPDX-License-Identifier: BSD-3-Clause * 21 | ****************************************************************************/ 22 | 23 | #ifndef TEST_OPENMP_CATEGORY_HPP 24 | #define TEST_OPENMP_CATEGORY_HPP 25 | 26 | #define TEST_CATEGORY openmp 27 | #define TEST_EXECSPACE Kokkos::OpenMP 28 | #define TEST_MEMSPACE Kokkos::HostSpace 29 | #define TEST_DEVICE Kokkos::Device 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /unit_test/TestSERIAL_Category.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | /**************************************************************************** 13 | * Copyright (c) 2018-2021 by the Cabana authors * 14 | * All rights reserved. * 15 | * * 16 | * This file is part of the Cabana library. Cabana is distributed under a * 17 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 18 | * the top-level directory. * 19 | * * 20 | * SPDX-License-Identifier: BSD-3-Clause * 21 | ****************************************************************************/ 22 | 23 | #ifndef TEST_SERIAL_CATEGORY_HPP 24 | #define TEST_SERIAL_CATEGORY_HPP 25 | 26 | #define TEST_CATEGORY serial 27 | #define TEST_EXECSPACE Kokkos::Serial 28 | #define TEST_MEMSPACE Kokkos::HostSpace 29 | #define TEST_DEVICE Kokkos::Device 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /unit_test/TestPTHREAD_Category.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | /**************************************************************************** 13 | * Copyright (c) 2018-2021 by the Cabana authors * 14 | * All rights reserved. * 15 | * * 16 | * This file is part of the Cabana library. Cabana is distributed under a * 17 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 18 | * the top-level directory. * 19 | * * 20 | * SPDX-License-Identifier: BSD-3-Clause * 21 | ****************************************************************************/ 22 | 23 | #ifndef TEST_PTHREAD_CATEGORY_HPP 24 | #define TEST_PTHREAD_CATEGORY_HPP 25 | 26 | #define TEST_CATEGORY pthread 27 | #define TEST_EXECSPACE Kokkos::Threads 28 | #define TEST_MEMSPACE Kokkos::HostSpace 29 | #define TEST_DEVICE Kokkos::Device 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/CabanaPD.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef CABANAPD_HPP 13 | #define CABANAPD_HPP 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /unit_test/TestHIP_Category.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | /**************************************************************************** 13 | * Copyright (c) 2018-2021 by the Cabana authors * 14 | * All rights reserved. * 15 | * * 16 | * This file is part of the Cabana library. Cabana is distributed under a * 17 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 18 | * the top-level directory. * 19 | * * 20 | * SPDX-License-Identifier: BSD-3-Clause * 21 | ****************************************************************************/ 22 | 23 | #ifndef TEST_HIP_CATEGORY_HPP 24 | #define TEST_HIP_CATEGORY_HPP 25 | 26 | #define TEST_CATEGORY hip 27 | #define TEST_EXECSPACE Kokkos::Experimental::HIP 28 | #define TEST_MEMSPACE Kokkos::Experimental::HIPSpace 29 | #define TEST_DEVICE \ 30 | Kokkos::Device 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /unit_test/mpi_unit_test_main.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | /**************************************************************************** 13 | * Copyright (c) 2018-2021 by the Cabana authors * 14 | * All rights reserved. * 15 | * * 16 | * This file is part of the Cabana library. Cabana is distributed under a * 17 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 18 | * the top-level directory. * 19 | * * 20 | * SPDX-License-Identifier: BSD-3-Clause * 21 | ****************************************************************************/ 22 | 23 | #include 24 | 25 | #include 26 | 27 | #include 28 | 29 | int main( int argc, char* argv[] ) 30 | { 31 | MPI_Init( &argc, &argv ); 32 | Kokkos::initialize( argc, argv ); 33 | ::testing::InitGoogleTest( &argc, argv ); 34 | int return_val = RUN_ALL_TESTS(); 35 | Kokkos::finalize(); 36 | MPI_Finalize(); 37 | return return_val; 38 | } 39 | -------------------------------------------------------------------------------- /src/CabanaPD_Timer.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef TIMER_H 13 | #define TIMER_H 14 | 15 | #include "mpi.h" 16 | #include 17 | #include 18 | 19 | namespace CabanaPD 20 | { 21 | class Timer 22 | { 23 | double _time = 0.0; 24 | double _start_time = 0.0; 25 | double _last_time = 0.0; 26 | double _max_time = 0.0; 27 | double _min_time = 0.0; 28 | int _num_calls = 0; 29 | bool _running = false; 30 | 31 | public: 32 | void start() 33 | { 34 | if ( _running ) 35 | throw std::runtime_error( "Timer already running" ); 36 | 37 | _start_time = MPI_Wtime(); 38 | _running = true; 39 | } 40 | void stop() 41 | { 42 | if ( !_running ) 43 | throw std::runtime_error( "Timer not running." ); 44 | 45 | _last_time = MPI_Wtime() - _start_time; 46 | _time += _last_time; 47 | _num_calls++; 48 | _running = false; 49 | } 50 | void reset() { _time = 0.0; } 51 | void set( const double val ) 52 | { 53 | _time = val; 54 | _last_time = val; 55 | } 56 | bool running() { return _running; } 57 | auto time() { return _time; } 58 | auto minTime() { return _min_time; } 59 | auto maxTime() { return _max_time; } 60 | auto numCalls() { return _num_calls; } 61 | auto lastTime() { return _last_time; } 62 | 63 | void reduceMPI() 64 | { 65 | MPI_Allreduce( &_time, &_max_time, 1, MPI_DOUBLE, MPI_MAX, 66 | MPI_COMM_WORLD ); 67 | MPI_Allreduce( &_time, &_min_time, 1, MPI_DOUBLE, MPI_MIN, 68 | MPI_COMM_WORLD ); 69 | } 70 | }; 71 | } // namespace CabanaPD 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /.github/workflows/Weekly.yml: -------------------------------------------------------------------------------- 1 | name: Weekly 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 5 * * 1' 6 | 7 | jobs: 8 | CI: 9 | strategy: 10 | matrix: 11 | backend: ["OPENMP", "SERIAL"] 12 | cmake_build_type: ['Release'] 13 | runs-on: ubuntu-latest 14 | container: ghcr.io/ecp-copa/ci-containers/ubuntu:latest 15 | steps: 16 | - name: Checkout kokkos 17 | uses: actions/checkout@v3 18 | with: 19 | repository: kokkos/kokkos 20 | ref: develop 21 | path: kokkos 22 | - name: Build kokkos 23 | working-directory: kokkos 24 | run: | 25 | cmake -B build \ 26 | -DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} \ 27 | -DCMAKE_INSTALL_PREFIX=$HOME/kokkos \ 28 | -DKokkos_ENABLE_${{ matrix.backend }}=ON \ 29 | -DKokkos_ENABLE_DEPRECATED_CODE_3=OFF \ 30 | -DKokkos_ENABLE_IMPL_VIEW_LEGACY=ON 31 | cmake --build build --parallel 2 32 | cmake --install build 33 | - name: Checkout Cabana 34 | uses: actions/checkout@v3 35 | with: 36 | repository: ECP-CoPA/Cabana 37 | ref: master 38 | path: cabana 39 | - name: Build Cabana 40 | working-directory: cabana 41 | run: | 42 | cmake -B build \ 43 | -DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} \ 44 | -DCMAKE_INSTALL_PREFIX=$HOME/Cabana \ 45 | -DCMAKE_PREFIX_PATH="$HOME/kokkos" \ 46 | -DCabana_REQUIRE_HDF5=ON \ 47 | -DCabana_REQUIRE_SILO=ON 48 | cmake --build build --parallel 2 49 | cmake --install build 50 | - name: Checkout CabanaPD 51 | uses: actions/checkout@v3 52 | - name: Build CabanaPD 53 | run: | 54 | cmake -B build \ 55 | -D CMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} \ 56 | -D CMAKE_CXX_FLAGS="-Wall -Wextra -pedantic -Werror" \ 57 | -D CMAKE_INSTALL_PREFIX=$HOME/CabanaPD \ 58 | -D CMAKE_PREFIX_PATH="$HOME/Cabana" \ 59 | -D MPIEXEC_MAX_NUMPROCS=2 \ 60 | -D MPIEXEC_PREFLAGS="--oversubscribe" \ 61 | -D CabanaPD_ENABLE_TESTING=ON \ 62 | -D CabanaPD_ENABLE_EXAMPLES=ON 63 | cmake --build build --parallel 2 64 | cmake --install build 65 | - name: Test CabanaPD 66 | run: | 67 | CTEST_OUTPUT_ON_FAILURE=1 cmake --build build --target test 68 | -------------------------------------------------------------------------------- /src/CabanaPD_BodyTerm.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef BODYTERM_H 13 | #define BODYTERM_H 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | namespace CabanaPD 23 | { 24 | 25 | template 26 | struct BodyTerm 27 | { 28 | UserFunctor _user_functor; 29 | std::size_t _particle_count; 30 | bool _force_update; 31 | bool _update_frozen; 32 | 33 | Timer _timer; 34 | 35 | BodyTerm( UserFunctor user, const std::size_t particle_count, 36 | const bool force, const bool update_frozen = false ) 37 | : _user_functor( user ) 38 | , _particle_count( particle_count ) 39 | , _force_update( force ) 40 | , _update_frozen( update_frozen ) 41 | { 42 | } 43 | 44 | // This function interface purposely matches the boundary conditions in 45 | // order to use the two interchangeably in Solvers. 46 | template 47 | void apply( ExecSpace, ParticleType& particles, const double time ) 48 | { 49 | checkParticleCount( _particle_count, particles.referenceOffset(), 50 | "BodyTerm" ); 51 | 52 | _timer.start(); 53 | std::size_t start = particles.frozenOffset(); 54 | if ( _update_frozen ) 55 | start = 0; 56 | Kokkos::RangePolicy policy( start, particles.localOffset() ); 57 | auto user = _user_functor; 58 | Kokkos::parallel_for( 59 | "CabanaPD::BodyTerm::apply", policy, 60 | KOKKOS_LAMBDA( const int p ) { user( p, time ); } ); 61 | Kokkos::fence(); 62 | _timer.stop(); 63 | } 64 | 65 | auto forceUpdate() { return _force_update; } 66 | 67 | auto time() { return _timer.time(); }; 68 | auto timeInit() { return 0.0; }; 69 | }; 70 | 71 | } // namespace CabanaPD 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /cmake/FindCLANG_FORMAT.cmake: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Copyright (c) 2018-2021 by the Cabana authors # 3 | # All rights reserved. # 4 | # # 5 | # This file is part of the Cabana library. Cabana is distributed under a # 6 | # BSD 3-clause license. For the licensing terms see the LICENSE file in # 7 | # the top-level directory. # 8 | # # 9 | # SPDX-License-Identifier: BSD-3-Clause # 10 | ############################################################################ 11 | # 12 | # Find clang-format 13 | # 14 | # CLANG_FORMAT_EXECUTABLE - Path to clang-format executable 15 | # CLANG_FORMAT_FOUND - True if the clang-format executable was found. 16 | # CLANG_FORMAT_VERSION - The version of clang-format found 17 | # 18 | 19 | find_program(CLANG_FORMAT_EXECUTABLE 20 | NAMES clang-format 21 | clang-format-7 22 | clang-format-6.0 23 | clang-format-5.0 24 | clang-format-4.0 25 | clang-format-3.9 26 | clang-format-3.8 27 | clang-format-3.7 28 | clang-format-3.6 29 | clang-format-3.5 30 | clang-format-3.4 31 | clang-format-3.3 32 | DOC "clang-format executable") 33 | mark_as_advanced(CLANG_FORMAT_EXECUTABLE) 34 | 35 | # Extract version from command "clang-format -version" 36 | if(CLANG_FORMAT_EXECUTABLE) 37 | execute_process(COMMAND ${CLANG_FORMAT_EXECUTABLE} -version 38 | OUTPUT_VARIABLE clang_format_version 39 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 40 | 41 | if(clang_format_version MATCHES "^.*clang-format version .*") 42 | # clang_format_version sample: "clang-format version 3.9.1-4ubuntu3~16.04.1 43 | # (tags/RELEASE_391/rc2)" 44 | string(REGEX 45 | REPLACE "^.*clang-format version ([.0-9]+).*" 46 | "\\1" 47 | CLANG_FORMAT_VERSION 48 | "${clang_format_version}") 49 | # CLANG_FORMAT_VERSION sample: "3.9.1" 50 | else() 51 | set(CLANG_FORMAT_VERSION 0.0) 52 | endif() 53 | else() 54 | set(CLANG_FORMAT_VERSION 0.0) 55 | endif() 56 | 57 | include(FindPackageHandleStandardArgs) 58 | # handle the QUIETLY and REQUIRED arguments and set CLANG_FORMAT_FOUND to TRUE 59 | # if all listed variables are TRUE 60 | find_package_handle_standard_args(CLANG_FORMAT REQUIRED_VARS CLANG_FORMAT_EXECUTABLE VERSION_VAR CLANG_FORMAT_VERSION) 61 | -------------------------------------------------------------------------------- /unit_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file( 2 | ${CMAKE_CURRENT_SOURCE_DIR}/inputs/hertzian_contact.json 3 | ${CMAKE_CURRENT_BINARY_DIR}/hertzian_contact.json 4 | COPYONLY 5 | ) 6 | configure_file( 7 | ${CMAKE_CURRENT_SOURCE_DIR}/inputs/hertzian_jkr_contact.json 8 | ${CMAKE_CURRENT_BINARY_DIR}/hertzian_jkr_contact.json 9 | COPYONLY 10 | ) 11 | ##--------------------------------------------------------------------------## 12 | ## On-node tests 13 | ##--------------------------------------------------------------------------## 14 | macro(CabanaPD_add_tests) 15 | cmake_parse_arguments(CABANAPD_UNIT_TEST "MPI" "" "NAMES" ${ARGN}) 16 | set(CABANAPD_UNIT_TEST_MPIEXEC_NUMPROCS 1) 17 | if(CABANAPD_UNIT_TEST_MPI) 18 | list(APPEND CABANAPD_UNIT_TEST_MPIEXEC_NUMPROCS 2) 19 | if(MPIEXEC_MAX_NUMPROCS GREATER 2) 20 | list(APPEND CABANAPD_UNIT_TEST_MPIEXEC_NUMPROCS ${MPIEXEC_MAX_NUMPROCS}) 21 | endif() 22 | endif() 23 | set(CABANAPD_UNIT_TEST_NUMTHREADS 1 2) 24 | 25 | set(CABANAPD_UNIT_TEST_MAIN mpi_unit_test_main.cpp) 26 | 27 | foreach(_device SERIAL PTHREAD OPENMP CUDA HIP) 28 | if(Kokkos_ENABLE_${_device}) 29 | string(TOUPPER ${_device} _uppercase_device) 30 | set(_dir ${CMAKE_CURRENT_BINARY_DIR}/${_uppercase_device}) 31 | file(MAKE_DIRECTORY ${_dir}) 32 | foreach(_test ${CABANAPD_UNIT_TEST_NAMES}) 33 | set(_file ${_dir}/tst${_test}_${_uppercase_device}.cpp) 34 | file(WRITE ${_file} 35 | "#include \n" 36 | "#include \n" 37 | ) 38 | set(_target ${_test}_test_${_uppercase_device}) 39 | add_executable(${_target} ${_file} ${CABANAPD_UNIT_TEST_MAIN}) 40 | target_include_directories(${_target} PRIVATE ${_dir} ${CMAKE_CURRENT_SOURCE_DIR}) 41 | target_link_libraries(${_target} PRIVATE CabanaPD ${gtest_target}) 42 | 43 | foreach(_np ${CABANAPD_UNIT_TEST_MPIEXEC_NUMPROCS}) 44 | if(_device STREQUAL PTHREAD OR _device STREQUAL OPENMP) 45 | foreach(_thread ${CABANAPD_UNIT_TEST_NUMTHREADS}) 46 | add_test(NAME ${_target}_NP_${_np}_NT_${_thread} COMMAND 47 | ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${_np} ${MPIEXEC_PREFLAGS} 48 | ${_target} ${MPIEXEC_POSTFLAGS} ${gtest_args} --kokkos-threads=${_thread}) 49 | endforeach() 50 | else() 51 | add_test(NAME ${_target}_NP_${_np} COMMAND 52 | ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${_np} ${MPIEXEC_PREFLAGS} 53 | ${_target} ${MPIEXEC_POSTFLAGsS} ${gtest_args}) 54 | endif() 55 | endforeach() 56 | endforeach() 57 | endif() 58 | endforeach() 59 | endmacro() 60 | 61 | CabanaPD_add_tests(NAMES Particles Force Integrator NormalRepulsion Hertz HertzJKR) 62 | 63 | CabanaPD_add_tests(MPI NAMES Comm) 64 | -------------------------------------------------------------------------------- /src/force_models/CabanaPD_Contact.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef CONTACTMODELS_H 13 | #define CONTACTMODELS_H 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace CabanaPD 22 | { 23 | /****************************************************************************** 24 | Contact model 25 | ******************************************************************************/ 26 | struct ContactModel 27 | { 28 | // Tag for creating particle fields. 29 | using model_tag = Contact; 30 | using material_type = SingleMaterial; 31 | 32 | // Contact neighbor search radius. 33 | double radius; 34 | // Extend neighbor search radius to reuse lists. 35 | double radius_extend; 36 | 37 | ContactModel() {} 38 | 39 | // PD horizon 40 | // Contact radius 41 | ContactModel( const double _radius, const double _radius_extend ) 42 | : radius( _radius ) 43 | , radius_extend( _radius_extend ){}; 44 | 45 | auto cutoff() const { return 2.0 * radius + radius_extend; } 46 | auto extend() const { return radius_extend; } 47 | 48 | void updateBonds( const int, const int ) {} 49 | }; 50 | 51 | /* Normal repulsion */ 52 | struct NormalRepulsionModel : public ContactModel 53 | { 54 | using base_type = ContactModel; 55 | using fracture_type = NoFracture; 56 | using thermal_type = TemperatureIndependent; 57 | // Tag to dispatch to force iteration. 58 | using force_tag = NormalRepulsionModel; 59 | 60 | double delta; 61 | using ContactModel::radius; 62 | using ContactModel::radius_extend; 63 | 64 | double c; 65 | double K; 66 | 67 | NormalRepulsionModel() {} 68 | NormalRepulsionModel( const double _delta, const double radius, 69 | const double radius_extend, const double _K ) 70 | : ContactModel( radius, radius_extend ) 71 | , delta( _delta ) 72 | , K( _K ) 73 | { 74 | K = _K; 75 | // This could inherit from PMB (same c) 76 | c = 18.0 * K / ( pi * delta * delta * delta * delta ); 77 | } 78 | 79 | KOKKOS_INLINE_FUNCTION 80 | auto forceCoeff( const double r, const double vol ) const 81 | { 82 | // Contact "stretch" 83 | const double sc = ( r - radius ) / delta; 84 | // Normal repulsion uses a 15 factor compared to the PMB force 85 | return 15.0 * c * sc * vol; 86 | } 87 | }; 88 | 89 | } // namespace CabanaPD 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /src/force_models/CabanaPD_Hertzian.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef CONTACTMODEL_HERTZIAN_H 13 | #define CONTACTMODEL_HERTZIAN_H 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace CabanaPD 22 | { 23 | struct HertzianModel : public ContactModel 24 | { 25 | using base_type = ContactModel; 26 | using fracture_type = NoFracture; 27 | using thermal_type = TemperatureIndependent; 28 | // Tag to dispatch to force iteration. 29 | using force_tag = HertzianModel; 30 | 31 | using base_type::radius; 32 | double nu; // Poisson's ratio 33 | double Rs; // Equivalent radius 34 | double Es; // Equivalent Young's modulus 35 | double e; // Coefficient of restitution 36 | double beta; // Damping coefficient 37 | double coeff_h_n; 38 | double coeff_h_d; 39 | 40 | HertzianModel() {} 41 | HertzianModel( const double _radius, const double _extend, const double _nu, 42 | const double _E, const double _e ) 43 | : base_type( _radius, _extend ) 44 | , nu( _nu ) 45 | { 46 | Rs = 0.5 * radius; 47 | Es = _E / ( 2.0 * ( 1.0 - Kokkos::pow( nu, 2.0 ) ) ); 48 | e = _e; 49 | double ln_e = Kokkos::log( e ); 50 | beta = -ln_e / Kokkos::sqrt( Kokkos::pow( ln_e, 2.0 ) + 51 | Kokkos::pow( pi, 2.0 ) ); 52 | 53 | // Derived constants. 54 | coeff_h_n = 4.0 / 3.0 * Es * Kokkos::sqrt( Rs ); 55 | coeff_h_d = -2.0 * Kokkos::sqrt( 5.0 / 6.0 ) * beta; 56 | } 57 | 58 | KOKKOS_INLINE_FUNCTION 59 | auto normalForce( const double r, const double vn, const double vol, 60 | const double rho ) const 61 | { 62 | // Contact "overlap" 63 | const double delta_n = ( r - 2.0 * radius ); 64 | 65 | // Hertz normal force coefficient 66 | double coeff = 0.0; 67 | if ( delta_n < 0.0 ) 68 | { 69 | coeff = Kokkos::min( 70 | 0.0, 71 | -coeff_h_n * Kokkos::pow( Kokkos::abs( delta_n ), 3.0 / 2.0 ) ); 72 | } 73 | coeff /= vol; 74 | 75 | // Damping force coefficient 76 | double Sn = 0.0; 77 | if ( delta_n < 0.0 ) 78 | Sn = 2.0 * Es * Kokkos::sqrt( Rs * Kokkos::abs( delta_n ) ); 79 | double ms = ( rho * vol ) / 2.0; 80 | coeff -= coeff_h_d * Kokkos::sqrt( Sn * ms ) * vn / vol; 81 | return coeff; 82 | } 83 | KOKKOS_INLINE_FUNCTION 84 | auto tangentialForce( const double, const double, const double, 85 | const double, double&, double&, double& ) const 86 | { 87 | } 88 | }; 89 | 90 | } // namespace CabanaPD 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 0.4.0 2 | 3 | ## New Features 4 | - Added discrete element method (DEM) support, either as stand-alone or as contact forces for peridynamics 5 | - Added support for multi-material systems (currently only using the same model form) 6 | - Added mechanics models with plasticity (elastic-perfectly plastic as a reference implementation) 7 | - Added heat transfer 8 | - Added optional calculation of peridynamic stress tensor per particle 9 | - Added interfaces for output of particle fields within subvolumes as a function of time 10 | - New examples for all new features above 11 | 12 | ## Bug Fixes and Improvements 13 | - Fixed CMake installs for downstream packages 14 | - Fixed bug for regions and boundary conditions that encompass >50% of the system 15 | - Added Kokkos fences for parallel consistency 16 | - Enabled pre-notches of any orientation 17 | - Enabled custom particle creation during class construction 18 | - Renamed model tags to clarify `Fracture` vs `NoFracture` and `Elastic` vs other mechanics models 19 | - Added output for total system damage 20 | - Fixed output for total system strain energy density (previously only from rank 0) 21 | - Added consistency checks for system size inputs 22 | - Added option to input m-ratio instead of horizon 23 | - Enforced consistent particle counts for boundary conditions and body terms 24 | - Made energy calculation and output optional 25 | 26 | ## Minimum dependency version updates 27 | - Cabana 0.7.0 or later is required 28 | 29 | 30 | # Version 0.3.0 31 | 32 | ## New Features 33 | - Added thermomechanics and associated example problems 34 | - Custom particle generation with user-functors 35 | - Added body terms as a special case of boundary conditions applied to the entire system 36 | - New fragmenting cylinder example 37 | 38 | ## Bug Fixes and Improvements 39 | - Fixed CMake version information 40 | - Improve generality of boundary conditions 41 | - Added options for linear profile output for particle properties 42 | - Added timestep selection functionality 43 | - Improved solver generality (boundary conditions for non-fracture, fracture without pre-cracks) 44 | 45 | ## Performance 46 | - Improved performance by reducing kernel launches in halo search 47 | 48 | ## Minimum dependency version updates 49 | - Cabana `master` is still required (post-release 0.6.1) 50 | 51 | 52 | # Version 0.2.0 53 | 54 | ## New Features 55 | - New example for crack branching 56 | - Generalization of boundary conditions (including for crack branching) 57 | - Addition of "no-fail" zone option (used for crack branching) 58 | - Added Cabana HDF5 particle output 59 | 60 | ## Bug Fixes and Improvements 61 | - Incorrect global particle count corrected (using `unsigned long long`) 62 | - Added continuous integration 63 | - Updated build for standard library/executable installation 64 | - Improved performance timers 65 | - Removed uses of `Cabana::Impl` functions 66 | 67 | ## Performance 68 | - Added `Cabana::Gather` with persistent communication buffers for substantial scalability improvement 69 | - Updates for force kernels with damage to reduce `if` statement branching 70 | 71 | ## Minimum dependency version updates 72 | - Cabana `master` is still required: now `31ba70d9` or newer (post-release 0.5.0) 73 | 74 | 75 | # Version 0.1.0 76 | 77 | ## New Features 78 | - Cabana particle data on regular grids 79 | - Multi-node Cabana particle migration 80 | - Cabana Verlet neighbor list 81 | - Prototype microelastic brittle (PMB) and linear peridynamic solid (LPS) force models 82 | - Velocity Verlet time integration 83 | - Pre-crack creation 84 | - Particle boundary conditions 85 | - Elastic wave propagation and Kalthoff-Winkler fracture examples 86 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project(CabanaPD LANGUAGES CXX C) 3 | set(PROJECT_VERSION "0.5.0-dev") 4 | 5 | cmake_policy(SET CMP0074 NEW) 6 | 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | set(CMAKE_CXX_STANDARD 14) 9 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CXX_FLAGS) 10 | #release comes with -O3 by default 11 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE) 12 | endif(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CXX_FLAGS) 13 | 14 | option(CMAKE_VERBOSE_MAKEFILE "Generate verbose Makefiles" OFF) 15 | include(GNUInstallDirs) 16 | include(CMakePackageConfigHelpers) 17 | 18 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 19 | 20 | write_basic_package_version_file("CabanaPDConfigVersion.cmake" 21 | VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) 22 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CabanaPD_Config.cmakein 23 | ${CMAKE_CURRENT_BINARY_DIR}/CabanaPDConfig.cmake @ONLY) 24 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CabanaPDConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/CabanaPDConfigVersion.cmake 25 | DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/CabanaPD) 26 | 27 | ##---------------------------------------------------------------------------## 28 | # Set up main options (inherit from Kokkos and Cabana CMake) 29 | ##---------------------------------------------------------------------------## 30 | find_package(Cabana REQUIRED 0.7.0) 31 | 32 | macro(CabanaPD_check_optional) 33 | cmake_parse_arguments(CABANA "" "OPTION" "" ${ARGN}) 34 | if( Cabana_ENABLE_${CABANA_OPTION} ) 35 | message( STATUS "Cabana includes ${CABANA_OPTION}" ) 36 | endif() 37 | endmacro() 38 | 39 | CabanaPD_check_optional( OPTION HDF5 ) 40 | CabanaPD_check_optional( OPTION SILO ) 41 | 42 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.24") 43 | cmake_policy(SET CMP0135 NEW) 44 | endif() 45 | find_package(nlohmann_json 3.10.0 QUIET) 46 | if(NOT NLOHMANN_JSON_FOUND) 47 | set(CabanaPD_INTERNAL_JSON ON) 48 | # Install for downstream packages. 49 | set(JSON_Install ON) 50 | include(FetchContent) 51 | # Using most recent release here 52 | FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz) 53 | FetchContent_MakeAvailable(json) 54 | endif() 55 | 56 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CabanaPD_Config.cmakein 57 | ${CMAKE_CURRENT_BINARY_DIR}/CabanaPD_Config.cmake @ONLY) 58 | 59 | ##---------------------------------------------------------------------------## 60 | ## Print the Git revision number to stdout 61 | ##---------------------------------------------------------------------------## 62 | FIND_PACKAGE(Git) 63 | IF(GIT_FOUND AND IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.git) 64 | EXECUTE_PROCESS( 65 | COMMAND ${GIT_EXECUTABLE} log --pretty=format:%H -n 1 66 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 67 | OUTPUT_VARIABLE CabanaPD_GIT_COMMIT_HASH 68 | ) 69 | ELSE() 70 | SET(CabanaPD_GIT_COMMIT_HASH "Not a git repository") 71 | ENDIF() 72 | MESSAGE(STATUS "CabanaPD Revision = '${CabanaPD_GIT_COMMIT_HASH}'") 73 | 74 | ##---------------------------------------------------------------------------## 75 | ## Build CabanaPD 76 | ##---------------------------------------------------------------------------## 77 | add_subdirectory(src) 78 | option(CabanaPD_ENABLE_EXAMPLES "Build examples" OFF) 79 | if(CabanaPD_ENABLE_EXAMPLES) 80 | add_subdirectory(examples) 81 | endif() 82 | 83 | ##---------------------------------------------------------------------------## 84 | ## Unit tests 85 | ##---------------------------------------------------------------------------## 86 | option(CabanaPD_ENABLE_TESTING "Build tests" OFF) 87 | if(CabanaPD_ENABLE_TESTING) 88 | find_package(GTest 1.10 REQUIRED) 89 | # Workaround for FindGTest module in CMake older than 3.20 90 | if(TARGET GTest::gtest) 91 | set(gtest_target GTest::gtest) 92 | elseif(TARGET GTest::GTest) 93 | set(gtest_target GTest::GTest) 94 | else() 95 | message(FATAL_ERROR "bug in GTest find module workaround") 96 | endif() 97 | enable_testing() 98 | add_subdirectory(unit_test) 99 | endif() 100 | 101 | ##---------------------------------------------------------------------------## 102 | ## Clang format 103 | ##---------------------------------------------------------------------------## 104 | find_package(CLANG_FORMAT) 105 | if(CLANG_FORMAT_FOUND) 106 | file(GLOB_RECURSE FORMAT_SOURCES src/*.[c,h]pp unit_test/*.[c,h]pp examples/*.[c,h]pp) 107 | add_custom_target(format 108 | COMMAND ${CLANG_FORMAT_EXECUTABLE} -i -style=file ${FORMAT_SOURCES} 109 | DEPENDS ${FORMAT_SOURCES}) 110 | endif() 111 | -------------------------------------------------------------------------------- /unit_test/tstIntegrator.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | /**************************************************************************** 13 | * Copyright (c) 2018-2021 by the Cabana authors * 14 | * All rights reserved. * 15 | * * 16 | * This file is part of the Cabana library. Cabana is distributed under a * 17 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 18 | * the top-level directory. * 19 | * * 20 | * SPDX-License-Identifier: BSD-3-Clause * 21 | ****************************************************************************/ 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | namespace Test 36 | { 37 | //---------------------------------------------------------------------------// 38 | void testIntegratorReversibility( int steps ) 39 | { 40 | using exec_space = TEST_EXECSPACE; 41 | 42 | std::array box_min = { -1.0, -1.0, -1.0 }; 43 | std::array box_max = { 1.0, 1.0, 1.0 }; 44 | std::array num_cells = { 10, 10, 10 }; 45 | 46 | CabanaPD::Particles particles( TEST_MEMSPACE{}, CabanaPD::PMB{}, 47 | CabanaPD::TemperatureIndependent{} ); 48 | particles.domain( box_min, box_max, num_cells, 0 ); 49 | particles.create( exec_space{} ); 50 | auto x = particles.sliceReferencePosition(); 51 | std::size_t num_particle = x.size(); 52 | 53 | CabanaPD::VelocityVerlet<> integrator( 0.001 ); 54 | 55 | // Keep a copy of initial positions on the host 56 | using DataTypes = Cabana::MemberTypes; 57 | using HostAoSoA = Cabana::AoSoA; 58 | HostAoSoA x_aosoa_init( "x_init_host", num_particle ); 59 | auto x_init = Cabana::slice<0>( x_aosoa_init ); 60 | Cabana::deep_copy( x_init, x ); 61 | 62 | // Integrate one step 63 | for ( int s = 0; s < steps; ++s ) 64 | { 65 | integrator.initialHalfStep( exec_space{}, particles ); 66 | integrator.finalHalfStep( exec_space{}, particles ); 67 | } 68 | 69 | // Reverse the system. 70 | auto v = particles.sliceVelocity(); 71 | Kokkos::RangePolicy exec_policy( 0, num_particle ); 72 | Kokkos::parallel_for( 73 | exec_policy, KOKKOS_LAMBDA( const int p ) { 74 | for ( int d = 0; d < 3; ++d ) 75 | v( p, d ) *= -1.0; 76 | } ); 77 | 78 | // Integrate back 79 | for ( int s = 0; s < steps; ++s ) 80 | { 81 | integrator.initialHalfStep( exec_space{}, particles ); 82 | integrator.finalHalfStep( exec_space{}, particles ); 83 | } 84 | 85 | // Make a copy of final results on the host 86 | HostAoSoA x_aosoa_final( "x_final_host", num_particle ); 87 | auto x_final = Cabana::slice<0>( x_aosoa_final ); 88 | Cabana::deep_copy( x_final, x ); 89 | 90 | // Check the results 91 | x = particles.sliceReferencePosition(); 92 | for ( std::size_t p = 0; p < num_particle; ++p ) 93 | for ( std::size_t d = 0; d < 3; ++d ) 94 | EXPECT_DOUBLE_EQ( x_final( p, d ), x_init( p, d ) ); 95 | } 96 | 97 | //---------------------------------------------------------------------------// 98 | // TESTS 99 | //---------------------------------------------------------------------------// 100 | TEST( TEST_CATEGORY, test_integrate_reversibility ) 101 | { 102 | testIntegratorReversibility( 100 ); 103 | } 104 | 105 | //---------------------------------------------------------------------------// 106 | 107 | } // end namespace Test 108 | -------------------------------------------------------------------------------- /examples/mechanics/elastic_wave.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate elastic wave propagation from an initial displacement field. 22 | void elasticWaveExample( const std::string filename ) 23 | { 24 | // ==================================================== 25 | // Choose Kokkos spaces 26 | // ==================================================== 27 | using exec_space = Kokkos::DefaultExecutionSpace; 28 | using memory_space = typename exec_space::memory_space; 29 | 30 | // ==================================================== 31 | // Read inputs 32 | // ==================================================== 33 | CabanaPD::Inputs inputs( filename ); 34 | 35 | // ==================================================== 36 | // Material parameters 37 | // ==================================================== 38 | double rho0 = inputs["density"]; 39 | auto K = inputs["bulk_modulus"]; 40 | double G = inputs["shear_modulus"]; 41 | double horizon = inputs["horizon"]; 42 | horizon += 1e-10; 43 | 44 | // ==================================================== 45 | // Discretization 46 | // ==================================================== 47 | std::array low_corner = inputs["low_corner"]; 48 | std::array high_corner = inputs["high_corner"]; 49 | std::array num_cells = inputs["num_cells"]; 50 | int m = std::floor( horizon / 51 | ( ( high_corner[0] - low_corner[0] ) / num_cells[0] ) ); 52 | int halo_width = m + 1; // Just to be safe. 53 | 54 | // ==================================================== 55 | // Force model 56 | // ==================================================== 57 | using model_type = CabanaPD::LinearLPS; 58 | CabanaPD::ForceModel force_model( model_type{}, CabanaPD::NoFracture{}, 59 | horizon, K, G ); 60 | 61 | // ==================================================== 62 | // Particle generation 63 | // ==================================================== 64 | CabanaPD::Particles particles( memory_space{}, model_type{} ); 65 | 66 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 67 | particles.create( exec_space{} ); 68 | 69 | // ==================================================== 70 | // Custom particle initialization 71 | // ==================================================== 72 | auto rho = particles.sliceDensity(); 73 | auto x = particles.sliceReferencePosition(); 74 | auto u = particles.sliceDisplacement(); 75 | auto v = particles.sliceVelocity(); 76 | 77 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 78 | { 79 | // Density 80 | rho( pid ) = rho0; 81 | 82 | // Initial conditions: displacements and velocities 83 | double a = 0.001; 84 | double r0 = 0.25; 85 | double l = 0.07; 86 | double norm = 87 | std::sqrt( x( pid, 0 ) * x( pid, 0 ) + x( pid, 1 ) * x( pid, 1 ) + 88 | x( pid, 2 ) * x( pid, 2 ) ); 89 | double diff = norm - r0; 90 | double arg = diff * diff / l / l; 91 | for ( int d = 0; d < 3; d++ ) 92 | { 93 | double comp = 0.0; 94 | if ( norm > 0.0 ) 95 | comp = x( pid, d ) / norm; 96 | u( pid, d ) = a * std::exp( -arg ) * comp; 97 | v( pid, d ) = 0.0; 98 | } 99 | }; 100 | particles.update( exec_space{}, init_functor ); 101 | 102 | // ==================================================== 103 | // Create solver 104 | // ==================================================== 105 | CabanaPD::Solver solver( inputs, particles, force_model ); 106 | 107 | // ==================================================== 108 | // Simulation run 109 | // ==================================================== 110 | solver.init(); 111 | solver.run(); 112 | 113 | // ==================================================== 114 | // Outputs 115 | // ==================================================== 116 | // Output x-displacement along the x-axis 117 | CabanaPD::createDisplacementProfile( "displacement_profile.txt", 118 | solver.particles, 0 ); 119 | } 120 | 121 | // Initialize MPI+Kokkos. 122 | int main( int argc, char* argv[] ) 123 | { 124 | MPI_Init( &argc, &argv ); 125 | Kokkos::initialize( argc, argv ); 126 | 127 | elasticWaveExample( argv[1] ); 128 | 129 | Kokkos::finalize(); 130 | MPI_Finalize(); 131 | } 132 | -------------------------------------------------------------------------------- /unit_test/tstHertz.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace Test 18 | { 19 | template 20 | double calculateKE( const VelType& v, const DensityType& rho, 21 | const VolumeType& vol ) 22 | { 23 | using Kokkos::hypot; 24 | using Kokkos::pow; 25 | 26 | using execution_space = typename VelType::execution_space; 27 | 28 | double tke; 29 | Kokkos::parallel_reduce( 30 | "total_ke", Kokkos::RangePolicy( 0, v.size() ), 31 | KOKKOS_LAMBDA( const int& i, double& sum ) { 32 | sum += 0.5 * rho( i ) * vol( i ) * 33 | pow( hypot( v( i, 0 ), v( i, 1 ), v( i, 2 ) ), 2.0 ); 34 | }, 35 | Kokkos::Sum( tke ) ); 36 | 37 | return tke; 38 | } 39 | 40 | void testHertzianContact( const std::string filename ) 41 | { 42 | // ==================================================== 43 | // Use test Kokkos spaces 44 | // ==================================================== 45 | using exec_space = TEST_EXECSPACE; 46 | using memory_space = TEST_MEMSPACE; 47 | 48 | // ==================================================== 49 | // Read inputs 50 | // ==================================================== 51 | CabanaPD::Inputs inputs( filename ); 52 | 53 | // ==================================================== 54 | // Material parameters 55 | // ==================================================== 56 | double rho0 = inputs["density"]; 57 | double vol = inputs["volume"]; 58 | double radius = inputs["radius"]; 59 | double radius_extend = inputs["radius_extend"]; 60 | double nu = inputs["poisson_ratio"]; 61 | double E = inputs["elastic_modulus"]; 62 | double e = inputs["restitution"]; 63 | 64 | // ==================================================== 65 | // Discretization 66 | // ==================================================== 67 | std::array low_corner = inputs["low_corner"]; 68 | std::array high_corner = inputs["high_corner"]; 69 | std::array num_cells = inputs["num_cells"]; 70 | 71 | // ==================================================== 72 | // Custom particle creation 73 | // ==================================================== 74 | const int num_particles = 2; 75 | // Purposely using zero-init here. 76 | Kokkos::View position( "custom_position", 2 ); 77 | Kokkos::View volume( "custom_volume", 2 ); 78 | 79 | Kokkos::parallel_for( 80 | "create_particles", Kokkos::RangePolicy( 0, num_particles ), 81 | KOKKOS_LAMBDA( const int p ) { 82 | if ( p == 0 ) 83 | position( p, 0 ) = 5.1e-5; 84 | else 85 | position( p, 0 ) = -5.1e-5; 86 | volume( p ) = vol; 87 | } ); 88 | 89 | // ==================================================== 90 | // Force model 91 | // ==================================================== 92 | using model_type = CabanaPD::HertzianModel; 93 | // No search radius extension. 94 | model_type contact_model( radius, radius_extend, nu, E, e ); 95 | 96 | // ==================================================== 97 | // Particle generation 98 | // ==================================================== 99 | int halo_width = 1; 100 | CabanaPD::Particles particles( memory_space{}, model_type{} ); 101 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 102 | particles.create( exec_space{}, position, volume ); 103 | 104 | // ==================================================== 105 | // Custom particle initialization 106 | // ==================================================== 107 | auto rho = particles.sliceDensity(); 108 | auto v = particles.sliceVelocity(); 109 | auto vo = particles.sliceVolume(); 110 | 111 | auto init_functor = KOKKOS_LAMBDA( const int p ) 112 | { 113 | // Density 114 | rho( p ) = rho0; 115 | if ( p == 0 ) 116 | v( p, 0 ) = -1.0; 117 | else 118 | v( p, 0 ) = 1.0; 119 | }; 120 | particles.update( exec_space{}, init_functor ); 121 | 122 | // Get initial total KE 123 | double ke_i = calculateKE( v, rho, vo ); 124 | 125 | // ==================================================== 126 | // Simulation run 127 | // ==================================================== 128 | CabanaPD::Solver solver( inputs, particles, contact_model ); 129 | solver.init(); 130 | solver.run(); 131 | 132 | // Get final total KE 133 | double ke_f = calculateKE( v, rho, vo ); 134 | 135 | EXPECT_NEAR( std::sqrt( ke_f / ke_i ), e, 1e-3 ); 136 | } 137 | 138 | // Test construction. 139 | TEST( TEST_CATEGORY, test_force_hertz_construct ) 140 | { 141 | double radius = 5.0; 142 | double extend = 1.0; 143 | double nu = 2.0; 144 | double E = 100.0; 145 | double e = 1.0; 146 | CabanaPD::HertzianModel contact_model( radius, extend, nu, E, e ); 147 | } 148 | 149 | TEST( TEST_CATEGORY, test_hertzian_contact ) 150 | { 151 | std::string input = "hertzian_contact.json"; 152 | testHertzianContact( input ); 153 | } 154 | 155 | } // end namespace Test 156 | -------------------------------------------------------------------------------- /src/CabanaPD_Types.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef TYPES_H 13 | #define TYPES_H 14 | 15 | #include 16 | 17 | namespace CabanaPD 18 | { 19 | // Fracture tags. 20 | struct NoFracture 21 | { 22 | }; 23 | struct Fracture 24 | { 25 | }; 26 | template 27 | struct is_fracture : public std::false_type 28 | { 29 | }; 30 | template <> 31 | struct is_fracture : public std::true_type 32 | { 33 | }; 34 | template 35 | struct is_fracture< 36 | ModelType, 37 | typename std::enable_if<( std::is_same::value )>::type> 39 | : public std::true_type 40 | { 41 | }; 42 | 43 | // Mechanics tags. 44 | struct Elastic 45 | { 46 | using base_type = Elastic; 47 | }; 48 | struct Plastic 49 | { 50 | using base_type = Plastic; 51 | }; 52 | struct ElasticPerfectlyPlastic 53 | { 54 | using base_type = Plastic; 55 | }; 56 | 57 | // Model category tags. 58 | struct Pair 59 | { 60 | }; 61 | struct State 62 | { 63 | }; 64 | template 65 | struct is_state_based : public std::false_type 66 | { 67 | }; 68 | template <> 69 | struct is_state_based : public std::true_type 70 | { 71 | }; 72 | template 73 | struct is_state_based< 74 | ModelType, 75 | typename std::enable_if<( 76 | std::is_same::value )>::type> 77 | : public std::true_type 78 | { 79 | }; 80 | 81 | // Material option tags. 82 | struct SingleMaterial 83 | { 84 | }; 85 | struct MultiMaterial 86 | { 87 | }; 88 | 89 | // Thermal tags. 90 | struct TemperatureIndependent 91 | { 92 | using base_type = TemperatureIndependent; 93 | }; 94 | struct TemperatureDependent 95 | { 96 | using base_type = TemperatureDependent; 97 | }; 98 | struct DynamicTemperature : public TemperatureDependent 99 | { 100 | using base_type = TemperatureDependent; 101 | }; 102 | 103 | //! Static type checkers. 104 | template 105 | struct is_temperature_dependent : public std::false_type 106 | { 107 | }; 108 | template <> 109 | struct is_temperature_dependent : public std::true_type 110 | { 111 | }; 112 | template <> 113 | struct is_temperature_dependent : public std::true_type 114 | { 115 | }; 116 | template 117 | struct is_heat_transfer : public std::false_type 118 | { 119 | }; 120 | template <> 121 | struct is_heat_transfer : public std::true_type 122 | { 123 | }; 124 | template 125 | struct is_temperature : public std::false_type 126 | { 127 | }; 128 | template <> 129 | struct is_temperature : public std::true_type 130 | { 131 | }; 132 | template <> 133 | struct is_temperature : public std::true_type 134 | { 135 | }; 136 | template <> 137 | struct is_temperature : public std::true_type 138 | { 139 | }; 140 | 141 | // Force model tags. 142 | struct PMB 143 | { 144 | using base_type = Pair; 145 | using model_tag = PMB; 146 | }; 147 | struct LinearPMB 148 | { 149 | using base_type = Pair; 150 | using model_tag = PMB; 151 | }; 152 | struct LPS 153 | { 154 | using base_type = State; 155 | using model_tag = LPS; 156 | }; 157 | struct LinearLPS 158 | { 159 | using base_type = State; 160 | using model_tag = LPS; 161 | }; 162 | 163 | // Contact and DEM (contact without PD) tags. 164 | struct Contact 165 | { 166 | using base_type = Pair; 167 | }; 168 | struct NoContact 169 | { 170 | using model_tag = std::false_type; 171 | using force_tag = std::false_type; 172 | using thermal_type = TemperatureIndependent; 173 | using fracture_type = NoFracture; 174 | }; 175 | template 176 | struct is_contact : public std::false_type 177 | { 178 | }; 179 | template 180 | struct is_contact< 181 | ModelType, 182 | typename std::enable_if<( 183 | std::is_same::value )>::type> 184 | : public std::true_type 185 | { 186 | }; 187 | 188 | template 189 | struct either_contact 190 | { 191 | using base_type = NoContact; 192 | }; 193 | template 194 | struct either_contact< 195 | Model1, Model2, 196 | typename std::enable_if<( is_contact::value || 197 | is_contact::value )>::type> 198 | { 199 | using base_type = Contact; 200 | }; 201 | 202 | // Output tags. 203 | struct BaseOutput 204 | { 205 | }; 206 | struct EnergyOutput 207 | { 208 | }; 209 | struct EnergyStressOutput 210 | { 211 | }; 212 | 213 | template 214 | struct is_output : public std::false_type 215 | { 216 | }; 217 | template <> 218 | struct is_output : public std::true_type 219 | { 220 | }; 221 | template <> 222 | struct is_output : public std::true_type 223 | { 224 | }; 225 | template <> 226 | struct is_output : public std::true_type 227 | { 228 | }; 229 | 230 | template 231 | struct is_energy_output : public std::false_type 232 | { 233 | }; 234 | template <> 235 | struct is_energy_output : public std::true_type 236 | { 237 | }; 238 | template <> 239 | struct is_energy_output : public std::true_type 240 | { 241 | }; 242 | 243 | template 244 | struct is_stress_output : public std::false_type 245 | { 246 | }; 247 | template <> 248 | struct is_stress_output : public std::true_type 249 | { 250 | }; 251 | 252 | // Particle init types. 253 | template 254 | struct is_particle_init : public std::false_type 255 | { 256 | }; 257 | template <> 258 | struct is_particle_init : public std::true_type 259 | { 260 | }; 261 | template <> 262 | struct is_particle_init : public std::true_type 263 | { 264 | }; 265 | 266 | } // namespace CabanaPD 267 | #endif 268 | -------------------------------------------------------------------------------- /examples/mechanics/kalthoff_winkler.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate the Kalthoff-Winkler experiment of crack propagation in 22 | // a pre-notched steel plate due to impact. 23 | void kalthoffWinklerExample( const std::string filename ) 24 | { 25 | // ==================================================== 26 | // Choose Kokkos spaces 27 | // ==================================================== 28 | using exec_space = Kokkos::DefaultExecutionSpace; 29 | using memory_space = typename exec_space::memory_space; 30 | 31 | // ==================================================== 32 | // Read inputs 33 | // ==================================================== 34 | CabanaPD::Inputs inputs( filename ); 35 | 36 | // ==================================================== 37 | // Material parameters 38 | // ==================================================== 39 | double rho0 = inputs["density"]; 40 | double E = inputs["elastic_modulus"]; 41 | double nu = 1.0 / 3.0; 42 | double K = E / ( 3.0 * ( 1.0 - 2.0 * nu ) ); 43 | double G0 = inputs["fracture_energy"]; 44 | // double G = E / ( 2.0 * ( 1.0 + nu ) ); // Only for LPS. 45 | double horizon = inputs["horizon"]; 46 | horizon += 1e-10; 47 | 48 | // ==================================================== 49 | // Discretization 50 | // ==================================================== 51 | std::array low_corner = inputs["low_corner"]; 52 | std::array high_corner = inputs["high_corner"]; 53 | std::array num_cells = inputs["num_cells"]; 54 | int m = std::floor( horizon / 55 | ( ( high_corner[0] - low_corner[0] ) / num_cells[0] ) ); 56 | int halo_width = m + 1; // Just to be safe. 57 | 58 | // ==================================================== 59 | // Pre-notches 60 | // ==================================================== 61 | std::array system_size = inputs["system_size"]; 62 | double height = system_size[0]; 63 | double width = system_size[1]; 64 | double thickness = system_size[2]; 65 | double L_prenotch = height / 2.0; 66 | double y_prenotch1 = -width / 8.0; 67 | double y_prenotch2 = width / 8.0; 68 | double low_x = low_corner[0]; 69 | double low_z = low_corner[2]; 70 | Kokkos::Array p01 = { low_x, y_prenotch1, low_z }; 71 | Kokkos::Array p02 = { low_x, y_prenotch2, low_z }; 72 | Kokkos::Array v1 = { L_prenotch, 0, 0 }; 73 | Kokkos::Array v2 = { 0, 0, thickness }; 74 | Kokkos::Array, 2> notch_positions = { p01, p02 }; 75 | CabanaPD::Prenotch<2> prenotch( v1, v2, notch_positions ); 76 | 77 | // ==================================================== 78 | // Force model 79 | // ==================================================== 80 | using model_type = CabanaPD::PMB; 81 | CabanaPD::ForceModel force_model( model_type{}, horizon, K, G0 ); 82 | // using model_type = CabanaPD::LPS; 83 | // CabanaPD::ForceModel force_model( model_type{}, horizon, K, G, G0 ); 84 | 85 | // ==================================================== 86 | // Particle generation 87 | // ==================================================== 88 | CabanaPD::Particles particles( memory_space{}, model_type{} ); 89 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 90 | particles.create( exec_space{} ); 91 | 92 | // ==================================================== 93 | // Custom particle initialization 94 | // ==================================================== 95 | auto rho = particles.sliceDensity(); 96 | auto x = particles.sliceReferencePosition(); 97 | auto v = particles.sliceVelocity(); 98 | auto f = particles.sliceForce(); 99 | 100 | double dx = particles.dx[0]; 101 | double v0 = inputs["impactor_velocity"]; 102 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 103 | { 104 | // Density 105 | rho( pid ) = rho0; 106 | // x velocity between the pre-notches 107 | if ( x( pid, 1 ) > y_prenotch1 && x( pid, 1 ) < y_prenotch2 && 108 | x( pid, 0 ) < -0.5 * height + dx ) 109 | v( pid, 0 ) = v0; 110 | }; 111 | particles.update( exec_space{}, init_functor ); 112 | 113 | // ==================================================== 114 | // Create solver 115 | // ==================================================== 116 | CabanaPD::Solver solver( inputs, particles, force_model ); 117 | 118 | // ==================================================== 119 | // Boundary conditions 120 | // ==================================================== 121 | // Create BC last to ensure ghost particles are included. 122 | double x_bc = -0.5 * height; 123 | CabanaPD::Region plane( 124 | x_bc - dx, x_bc + dx, y_prenotch1 - 0.25 * dx, y_prenotch2 + 0.25 * dx, 125 | -thickness, thickness ); 126 | auto bc = createBoundaryCondition( CabanaPD::ForceValueBCTag{}, 0.0, 127 | exec_space{}, solver.particles, plane ); 128 | 129 | // ==================================================== 130 | // Simulation run 131 | // ==================================================== 132 | solver.init( bc, prenotch ); 133 | solver.run( bc ); 134 | } 135 | 136 | // Initialize MPI+Kokkos. 137 | int main( int argc, char* argv[] ) 138 | { 139 | MPI_Init( &argc, &argv ); 140 | Kokkos::initialize( argc, argv ); 141 | 142 | kalthoffWinklerExample( argv[1] ); 143 | 144 | Kokkos::finalize(); 145 | MPI_Finalize(); 146 | } 147 | -------------------------------------------------------------------------------- /examples/thermomechanics/thermal_deformation.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate thermally-induced deformation in a rectangular plate. 22 | void thermalDeformationExample( const std::string filename ) 23 | { 24 | // ==================================================== 25 | // Choose Kokkos spaces 26 | // ==================================================== 27 | using exec_space = Kokkos::DefaultExecutionSpace; 28 | using memory_space = typename exec_space::memory_space; 29 | 30 | // ==================================================== 31 | // Read inputs 32 | // ==================================================== 33 | CabanaPD::Inputs inputs( filename ); 34 | 35 | // ==================================================== 36 | // Material and problem parameters 37 | // ==================================================== 38 | // Material parameters 39 | double rho0 = inputs["density"]; 40 | double E = inputs["elastic_modulus"]; 41 | double nu = 0.25; 42 | double K = E / ( 3 * ( 1 - 2 * nu ) ); 43 | double horizon = inputs["horizon"]; 44 | horizon += 1e-10; 45 | double alpha = inputs["thermal_expansion_coeff"]; 46 | 47 | // Problem parameters 48 | double temp0 = inputs["reference_temperature"]; 49 | 50 | // ==================================================== 51 | // Discretization 52 | // ==================================================== 53 | std::array low_corner = inputs["low_corner"]; 54 | std::array high_corner = inputs["high_corner"]; 55 | std::array num_cells = inputs["num_cells"]; 56 | int m = std::floor( horizon / 57 | ( ( high_corner[0] - low_corner[0] ) / num_cells[0] ) ); 58 | int halo_width = m + 1; // Just to be safe. 59 | 60 | // ==================================================== 61 | // Force model type 62 | // ==================================================== 63 | using model_type = CabanaPD::PMB; 64 | using thermal_type = CabanaPD::TemperatureDependent; 65 | 66 | // ==================================================== 67 | // Particle generation 68 | // ==================================================== 69 | // Does not set displacements, velocities, etc. 70 | CabanaPD::Particles particles( memory_space{}, model_type{}, 71 | thermal_type{} ); 72 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 73 | particles.create( exec_space{} ); 74 | 75 | // ==================================================== 76 | // Custom particle initialization 77 | // ==================================================== 78 | auto rho = particles.sliceDensity(); 79 | auto init_functor = KOKKOS_LAMBDA( const int pid ) { rho( pid ) = rho0; }; 80 | particles.update( exec_space{}, init_functor ); 81 | 82 | // ==================================================== 83 | // Force model 84 | // ==================================================== 85 | auto temp = particles.sliceTemperature(); 86 | CabanaPD::ForceModel force_model( model_type{}, CabanaPD::NoFracture{}, 87 | horizon, K, temp, alpha, temp0 ); 88 | 89 | // ==================================================== 90 | // Create solver 91 | // ==================================================== 92 | CabanaPD::Solver solver( inputs, particles, force_model ); 93 | 94 | // ==================================================== 95 | // Imposed field 96 | // ==================================================== 97 | auto x = solver.particles.sliceReferencePosition(); 98 | temp = solver.particles.sliceTemperature(); 99 | const double low_corner_y = low_corner[1]; 100 | // This is purposely delayed until after solver init so that ghosted 101 | // particles are correctly taken into account for lambda capture here. 102 | auto temp_func = KOKKOS_LAMBDA( const int pid, const double t ) 103 | { 104 | temp( pid ) = temp0 + 5000.0 * ( x( pid, 1 ) - low_corner_y ) * t; 105 | }; 106 | CabanaPD::BodyTerm body_term( temp_func, solver.particles.size(), false ); 107 | 108 | // ==================================================== 109 | // Simulation run 110 | // ==================================================== 111 | solver.init( body_term ); 112 | solver.run( body_term ); 113 | 114 | // ==================================================== 115 | // Outputs 116 | // ==================================================== 117 | // Output y-displacement along the x-axis 118 | CabanaPD::createDisplacementProfile( "ydisplacement_xaxis_profile.txt", 119 | solver.particles, 0, 1 ); 120 | 121 | // Output y-displacement along the y-axis 122 | CabanaPD::createDisplacementProfile( "ydisplacement_yaxis_profile.txt", 123 | solver.particles, 1, 1 ); 124 | 125 | // Output displacement magnitude along the x-axis 126 | CabanaPD::createDisplacementMagnitudeProfile( 127 | "displacement_magnitude_xaxis_profile.txt", solver.particles, 0 ); 128 | 129 | // Output displacement magnitude along the y-axis 130 | CabanaPD::createDisplacementMagnitudeProfile( 131 | "displacement_magnitude_yaxis_profile.txt", solver.particles, 1 ); 132 | } 133 | 134 | // Initialize MPI+Kokkos. 135 | int main( int argc, char* argv[] ) 136 | { 137 | MPI_Init( &argc, &argv ); 138 | Kokkos::initialize( argc, argv ); 139 | 140 | thermalDeformationExample( argv[1] ); 141 | 142 | Kokkos::finalize(); 143 | MPI_Finalize(); 144 | } 145 | -------------------------------------------------------------------------------- /examples/mechanics/crack_branching.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate crack branching from an pre-crack. 22 | void crackBranchingExample( const std::string filename ) 23 | { 24 | // ==================================================== 25 | // Choose Kokkos spaces 26 | // ==================================================== 27 | using exec_space = Kokkos::DefaultExecutionSpace; 28 | using memory_space = typename exec_space::memory_space; 29 | 30 | // ==================================================== 31 | // Read inputs 32 | // ==================================================== 33 | CabanaPD::Inputs inputs( filename ); 34 | 35 | // ==================================================== 36 | // Material parameters 37 | // ==================================================== 38 | double rho0 = inputs["density"]; 39 | double E = inputs["elastic_modulus"]; 40 | double nu = 0.25; // Use bond-based model 41 | double K = E / ( 3 * ( 1 - 2 * nu ) ); 42 | double G0 = inputs["fracture_energy"]; 43 | double horizon = inputs["horizon"]; 44 | horizon += 1e-10; 45 | 46 | // ==================================================== 47 | // Discretization 48 | // ==================================================== 49 | std::array low_corner = inputs["low_corner"]; 50 | std::array high_corner = inputs["high_corner"]; 51 | 52 | // ==================================================== 53 | // Pre-notch 54 | // ==================================================== 55 | double height = inputs["system_size"][0]; 56 | double thickness = inputs["system_size"][2]; 57 | double L_prenotch = height / 2.0; 58 | double y_prenotch = 0.0; 59 | Kokkos::Array p01 = { low_corner[0], y_prenotch, low_corner[2] }; 60 | Kokkos::Array v1 = { L_prenotch, 0, 0 }; 61 | Kokkos::Array v2 = { 0, 0, thickness }; 62 | Kokkos::Array, 1> notch_positions = { p01 }; 63 | CabanaPD::Prenotch<1> prenotch( v1, v2, notch_positions ); 64 | 65 | // ==================================================== 66 | // Force model 67 | // ==================================================== 68 | using model_type = CabanaPD::PMB; 69 | CabanaPD::ForceModel force_model( model_type{}, horizon, K, G0 ); 70 | 71 | // ==================================================== 72 | // Particle generation 73 | // ==================================================== 74 | CabanaPD::Particles particles( memory_space{}, model_type{} ); 75 | 76 | // Note that individual inputs can be passed instead (see other examples). 77 | particles.domain( inputs ); 78 | particles.create( exec_space{} ); 79 | 80 | // ==================================================== 81 | // Boundary conditions planes 82 | // ==================================================== 83 | double dy = particles.dx[1]; 84 | CabanaPD::Region plane1( 85 | low_corner[0], high_corner[0], low_corner[1] - dy, low_corner[1] + dy, 86 | low_corner[2], high_corner[2] ); 87 | CabanaPD::Region plane2( 88 | low_corner[0], high_corner[0], high_corner[1] - dy, high_corner[1] + dy, 89 | low_corner[2], high_corner[2] ); 90 | 91 | // ==================================================== 92 | // Custom particle initialization 93 | // ==================================================== 94 | auto rho = particles.sliceDensity(); 95 | auto x = particles.sliceReferencePosition(); 96 | auto v = particles.sliceVelocity(); 97 | auto f = particles.sliceForce(); 98 | auto nofail = particles.sliceNoFail(); 99 | 100 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 101 | { 102 | // Density 103 | rho( pid ) = rho0; 104 | // No-fail zone 105 | if ( x( pid, 1 ) <= plane1.low[1] + horizon + 1e-10 || 106 | x( pid, 1 ) >= plane2.high[1] - horizon - 1e-10 ) 107 | nofail( pid ) = 1; 108 | }; 109 | particles.update( exec_space{}, init_functor ); 110 | 111 | // ==================================================== 112 | // Create solver 113 | // ==================================================== 114 | CabanaPD::Solver solver( inputs, particles, force_model ); 115 | 116 | // ==================================================== 117 | // Boundary conditions 118 | // ==================================================== 119 | // Create BC last to ensure ghost particles are included. 120 | double sigma0 = inputs["traction"]; 121 | double b0 = sigma0 / dy; 122 | f = solver.particles.sliceForce(); 123 | x = solver.particles.sliceReferencePosition(); 124 | // Create a symmetric force BC in the y-direction. 125 | auto bc_op = KOKKOS_LAMBDA( const int pid, const double ) 126 | { 127 | auto ypos = x( pid, 1 ); 128 | auto sign = std::abs( ypos ) / ypos; 129 | f( pid, 1 ) += b0 * sign; 130 | }; 131 | auto bc = createBoundaryCondition( bc_op, exec_space{}, solver.particles, 132 | true, plane1, plane2 ); 133 | 134 | // ==================================================== 135 | // Simulation run 136 | // ==================================================== 137 | solver.init( bc, prenotch ); 138 | solver.run( bc ); 139 | } 140 | 141 | // Initialize MPI+Kokkos. 142 | int main( int argc, char* argv[] ) 143 | { 144 | MPI_Init( &argc, &argv ); 145 | Kokkos::initialize( argc, argv ); 146 | 147 | crackBranchingExample( argv[1] ); 148 | 149 | Kokkos::finalize(); 150 | MPI_Finalize(); 151 | } 152 | -------------------------------------------------------------------------------- /examples/dem/powder_fill.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate powder settling. 22 | void powderSettlingExample( const std::string filename ) 23 | { 24 | // ==================================================== 25 | // Choose Kokkos spaces 26 | // ==================================================== 27 | using exec_space = Kokkos::DefaultExecutionSpace; 28 | using memory_space = typename exec_space::memory_space; 29 | 30 | // ==================================================== 31 | // Read inputs 32 | // ==================================================== 33 | CabanaPD::Inputs inputs( filename ); 34 | 35 | // ==================================================== 36 | // Material parameters 37 | // ==================================================== 38 | double rho0 = inputs["density"]; 39 | double vol0 = inputs["volume"]; 40 | double radius = inputs["radius"]; 41 | double radius_extend = inputs["radius_extend"]; 42 | double nu = inputs["poisson_ratio"]; 43 | double E = inputs["elastic_modulus"]; 44 | double e = inputs["restitution"]; 45 | 46 | // ==================================================== 47 | // Discretization 48 | // ==================================================== 49 | std::array low_corner = inputs["low_corner"]; 50 | std::array high_corner = inputs["high_corner"]; 51 | std::array num_cells = inputs["num_cells"]; 52 | int halo_width = 1; 53 | 54 | // ==================================================== 55 | // Force model 56 | // ==================================================== 57 | using model_type = CabanaPD::HertzianModel; 58 | model_type contact_model( radius, radius_extend, nu, E, e ); 59 | 60 | // ==================================================== 61 | // Custom particle initialization 62 | // ==================================================== 63 | double diameter = inputs["cylinder_diameter"]; 64 | double cylinder_radius = 0.5 * diameter; 65 | double wall_thickness = inputs["wall_thickness"]; 66 | double bottom = low_corner[2]; 67 | 68 | CabanaPD::Particles particles( memory_space{}, model_type{}, 69 | CabanaPD::BaseOutput{} ); 70 | // Create container. 71 | auto create_container = KOKKOS_LAMBDA( const int, const double x[3] ) 72 | { 73 | double rsq = x[0] * x[0] + x[1] * x[1]; 74 | 75 | // Convert domain block into cylinder 76 | if ( rsq > cylinder_radius * cylinder_radius ) 77 | return false; 78 | // Leave remaining bottom wall particles and remove particles inside 79 | // cylinder 80 | if ( x[2] > bottom + wall_thickness && 81 | rsq < ( cylinder_radius - wall_thickness ) * 82 | ( cylinder_radius - wall_thickness ) ) 83 | return false; 84 | 85 | return true; 86 | }; 87 | // Container particles should be frozen, never updated. 88 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 89 | particles.create( exec_space{}, create_container, 0, true ); 90 | 91 | // Create powder. 92 | double min_height = inputs["min_height"]; 93 | auto create_powder = KOKKOS_LAMBDA( const int, const double x[3] ) 94 | { 95 | double rsq = x[0] * x[0] + x[1] * x[1]; 96 | 97 | // Only create particles inside cylinder. 98 | if ( x[2] > min_height && 99 | rsq < ( cylinder_radius - wall_thickness ) * 100 | ( cylinder_radius - wall_thickness ) ) 101 | return true; 102 | 103 | return false; 104 | }; 105 | particles.create( exec_space{}, Cabana::InitRandom{}, create_powder, 106 | particles.numFrozen() ); 107 | 108 | // Set density/volumes. 109 | auto rho = particles.sliceDensity(); 110 | auto vol = particles.sliceVolume(); 111 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 112 | { 113 | rho( pid ) = rho0; 114 | vol( pid ) = vol0; 115 | }; 116 | particles.update( exec_space{}, init_functor ); 117 | 118 | // ==================================================== 119 | // Create solver 120 | // ==================================================== 121 | CabanaPD::Solver solver( inputs, particles, contact_model ); 122 | 123 | // ==================================================== 124 | // Simulation init 125 | // ==================================================== 126 | solver.init(); 127 | 128 | // Use a force magnitude threshold to remove particles that are too close. 129 | // TODO: The force magnitude should be based on the maximum desired overlap 130 | // according to the properties of the contact model 131 | solver.remove( 1e6 ); 132 | 133 | // ==================================================== 134 | // Boundary condition 135 | // ==================================================== 136 | auto f = solver.particles.sliceForce(); 137 | rho = solver.particles.sliceDensity(); 138 | auto body_functor = KOKKOS_LAMBDA( const int pid, const double ) 139 | { 140 | f( pid, 2 ) -= 9.8 * rho( pid ); 141 | }; 142 | CabanaPD::BodyTerm gravity( body_functor, solver.particles.size(), true ); 143 | 144 | // ==================================================== 145 | // Simulation run 146 | // ==================================================== 147 | solver.run( gravity ); 148 | } 149 | 150 | // Initialize MPI+Kokkos. 151 | int main( int argc, char* argv[] ) 152 | { 153 | MPI_Init( &argc, &argv ); 154 | Kokkos::initialize( argc, argv ); 155 | 156 | powderSettlingExample( argv[1] ); 157 | 158 | Kokkos::finalize(); 159 | MPI_Finalize(); 160 | } 161 | -------------------------------------------------------------------------------- /unit_test/tstHertzJKR.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace Test 18 | { 19 | template 20 | double getPulloffForce( const ForceType& f, double vol ) 21 | { 22 | using execution_space = typename ForceType::execution_space; 23 | 24 | double pulloff_force; 25 | auto min_po = Kokkos::Min( pulloff_force ); 26 | 27 | Kokkos::parallel_reduce( 28 | "pulloff_force", Kokkos::RangePolicy( 0, f.size() ), 29 | KOKKOS_LAMBDA( const int t, double& min ) { 30 | min_po.join( min, f( t ) ); 31 | }, 32 | min_po ); 33 | 34 | return pulloff_force * vol; 35 | } 36 | 37 | void testHertzianJKRContact( const std::string filename ) 38 | { 39 | // ==================================================== 40 | // Use test Kokkos spaces 41 | // ==================================================== 42 | using exec_space = TEST_EXECSPACE; 43 | using memory_space = TEST_MEMSPACE; 44 | 45 | // ==================================================== 46 | // Read inputs 47 | // ==================================================== 48 | CabanaPD::Inputs inputs( filename ); 49 | 50 | // ==================================================== 51 | // Material parameters 52 | // ==================================================== 53 | double rho0 = inputs["density"]; 54 | double vol = inputs["volume"]; 55 | double radius = inputs["radius"]; 56 | double radius_extend = inputs["radius_extend"]; 57 | double nu = inputs["poisson_ratio"]; 58 | double E = inputs["elastic_modulus"]; 59 | double e = inputs["restitution"]; 60 | double gamma = inputs["surface_adhesion"]; 61 | 62 | // ==================================================== 63 | // Discretization 64 | // ==================================================== 65 | std::array low_corner = inputs["low_corner"]; 66 | std::array high_corner = inputs["high_corner"]; 67 | std::array num_cells = inputs["num_cells"]; 68 | 69 | // ==================================================== 70 | // Custom particle creation 71 | // ==================================================== 72 | const int num_particles = 2; 73 | // Purposely using zero-init here. 74 | Kokkos::View position( "custom_position", 2 ); 75 | Kokkos::View volume( "custom_volume", 2 ); 76 | 77 | Kokkos::parallel_for( 78 | "create_particles", Kokkos::RangePolicy( 0, num_particles ), 79 | KOKKOS_LAMBDA( const int p ) { 80 | if ( p == 0 ) 81 | position( p, 0 ) = 5.01e-5; 82 | else 83 | position( p, 0 ) = -5.01e-5; 84 | volume( p ) = vol; 85 | } ); 86 | 87 | // ==================================================== 88 | // Force model 89 | // ==================================================== 90 | using model_type = CabanaPD::HertzianJKRModel; 91 | // No search radius extension. 92 | double mu = 0.0; 93 | model_type contact_model( radius, radius_extend, nu, E, e, gamma, mu ); 94 | 95 | // ==================================================== 96 | // Particle generation 97 | // ==================================================== 98 | int halo_width = 1; 99 | CabanaPD::Particles particles( memory_space{}, model_type{} ); 100 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 101 | particles.create( exec_space{}, position, volume ); 102 | 103 | // ==================================================== 104 | // Custom particle initialization 105 | // ==================================================== 106 | auto rho = particles.sliceDensity(); 107 | auto v = particles.sliceVelocity(); 108 | auto vo = particles.sliceVolume(); 109 | 110 | auto init_functor = KOKKOS_LAMBDA( const int p ) 111 | { 112 | // Density 113 | rho( p ) = rho0; 114 | if ( p == 0 ) 115 | v( p, 0 ) = -1.0; 116 | else 117 | v( p, 0 ) = 1.0; 118 | }; 119 | particles.update( exec_space{}, init_functor ); 120 | 121 | // ==================================================== 122 | // Simulation run 123 | // ==================================================== 124 | CabanaPD::Solver solver( inputs, particles, contact_model ); 125 | solver.init(); 126 | 127 | Kokkos::View force_time( "forces", 128 | solver.num_steps ); 129 | 130 | auto force = particles.sliceForce(); 131 | for ( int step = 1; step <= solver.num_steps; ++step ) 132 | { 133 | solver.runStep( step ); 134 | 135 | Kokkos::parallel_for( 136 | "extract_force", Kokkos::RangePolicy( 0, 1 ), 137 | KOKKOS_LAMBDA( const int ) { 138 | force_time( step - 1 ) = force( 0, 0 ); 139 | } ); 140 | } 141 | 142 | double min_po = getPulloffForce( force_time, vol ); 143 | double min_po_a = contact_model.fc; 144 | 145 | EXPECT_NEAR( min_po / min_po_a, -1.0, 5e-3 ); 146 | 147 | // TODO: We should also test with some amount of damping enabled, similar to 148 | // the plain Hertz unit test. 149 | } 150 | 151 | // Test construction. 152 | TEST( TEST_CATEGORY, test_force_jkr_construct ) 153 | { 154 | double radius = 5.0; 155 | double extend = 1.0; 156 | double nu = 2.0; 157 | double E = 100.0; 158 | double e = 1.0; 159 | double gamma = 1.0; 160 | double mu = 0.0; 161 | CabanaPD::HertzianJKRModel contact_model( radius, extend, nu, E, e, gamma, 162 | mu ); 163 | } 164 | 165 | TEST( TEST_CATEGORY, test_hertzian_jkr_contact ) 166 | { 167 | std::string input = "hertzian_jkr_contact.json"; 168 | testHertzianJKRContact( input ); 169 | } 170 | 171 | } // end namespace Test 172 | -------------------------------------------------------------------------------- /src/CabanaPD_ForceModelsMulti.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef FORCE_MODELS_MULTI_H 13 | #define FORCE_MODELS_MULTI_H 14 | 15 | #include 16 | 17 | namespace CabanaPD 18 | { 19 | 20 | /****************************************************************************** 21 | Multi-material models 22 | ******************************************************************************/ 23 | struct AverageTag 24 | { 25 | }; 26 | 27 | // Wrap multiple models in a single object. 28 | // TODO: this currently only supports bi-material systems. 29 | template 31 | struct ForceModels 32 | { 33 | using material_type = MultiMaterial; 34 | 35 | using first_model = ModelType1; 36 | using force_tag = typename first_model::force_tag; 37 | using model_tag = typename first_model::model_tag; 38 | using thermal_type = typename first_model::thermal_type; 39 | using fracture_type = typename first_model::fracture_type; 40 | 41 | static_assert( ( std::is_same::value || 43 | std::is_same::value || 45 | std::is_same::value ), 47 | "Thermomechanics does not yet support multiple materials!" ); 48 | 49 | ForceModels( MaterialType t, const ModelType1 m1, ModelType2 m2, 50 | ModelType12 m12 ) 51 | : type( t ) 52 | , model1( m1 ) 53 | , model2( m2 ) 54 | , model12( m12 ) 55 | { 56 | setForceHorizon(); 57 | } 58 | 59 | auto cutoff() const { return force_horizon; } 60 | 61 | void updateBonds( const int num_local, const int max_neighbors ) 62 | { 63 | model1.updateBonds( num_local, max_neighbors ); 64 | model2.updateBonds( num_local, max_neighbors ); 65 | model12.updateBonds( num_local, max_neighbors ); 66 | } 67 | 68 | KOKKOS_INLINE_FUNCTION int getIndex( const int i, const int j ) const 69 | { 70 | const int type_i = type( i ); 71 | const int type_j = type( j ); 72 | // FIXME: only for binary. 73 | if ( type_i == type_j ) 74 | return type_i; 75 | else 76 | return 2; 77 | } 78 | 79 | // Extract model index and hide template indexing. 80 | template 81 | KOKKOS_INLINE_FUNCTION auto operator()( Tag tag, const int i, const int j, 82 | Args... args ) const 83 | { 84 | auto t = getIndex( i, j ); 85 | // Call individual model. 86 | if ( t == 0 ) 87 | return model1( tag, i, j, std::forward( args )... ); 88 | else if ( t == 1 ) 89 | return model2( tag, i, j, std::forward( args )... ); 90 | else if ( t == 2 ) 91 | return model12( tag, i, j, std::forward( args )... ); 92 | else 93 | Kokkos::abort( "Invalid model index." ); 94 | } 95 | 96 | // This is only for LPS force/energy, currently the only cases that require 97 | // type information. When running models individually, the SingleMaterial 98 | // tag is used in the model directly; here it is replaced with the 99 | // MultiMaterial tag instead. 100 | template 101 | KOKKOS_INLINE_FUNCTION auto operator()( Tag tag, SingleMaterial, 102 | const int i, const int j, 103 | Args... args ) const 104 | { 105 | const int type_i = type( i ); 106 | const int type_j = type( j ); 107 | 108 | auto t = getIndex( i, j ); 109 | MultiMaterial mtag; 110 | // Call individual model. 111 | if ( t == 0 ) 112 | return model1( tag, mtag, type_i, type_j, 113 | std::forward( args )... ); 114 | else if ( t == 1 ) 115 | return model2( tag, mtag, type_i, type_j, 116 | std::forward( args )... ); 117 | else if ( t == 2 ) 118 | return model12( tag, mtag, type_i, type_j, 119 | std::forward( args )... ); 120 | else 121 | Kokkos::abort( "Invalid model index." ); 122 | } 123 | 124 | void update( const MaterialType _type ) { type = _type; } 125 | 126 | double force_horizon; 127 | MaterialType type; 128 | ModelType1 model1; 129 | ModelType2 model2; 130 | ModelType12 model12; 131 | 132 | protected: 133 | void setForceHorizon() 134 | { 135 | // Enforce equal cutoff for now. 136 | force_horizon = model1.force_horizon; 137 | checkHorizon( model2 ); 138 | checkHorizon( model12 ); 139 | } 140 | 141 | template 142 | auto checkHorizon( Model m, const double tol = 1e-10 ) 143 | { 144 | if ( std::abs( m.force_horizon - force_horizon ) > tol ) 145 | log_err( std::cout, "Horizon for each model must match for " 146 | "multi-material systems." ); 147 | } 148 | }; 149 | 150 | template 152 | auto createMultiForceModel( ParticleType particles, ModelType1 m1, 153 | ModelType2 m2, ModelType12 m12 ) 154 | { 155 | auto type = particles.sliceType(); 156 | return ForceModels( type, m1, m2, m12 ); 157 | } 158 | 159 | template 160 | auto createMultiForceModel( ParticleType particles, AverageTag, ModelType1 m1, 161 | ModelType2 m2 ) 162 | { 163 | ModelType1 m12( m1, m2 ); 164 | 165 | auto type = particles.sliceType(); 166 | return ForceModels( type, m1, m2, m12 ); 167 | } 168 | 169 | } // namespace CabanaPD 170 | 171 | #endif 172 | -------------------------------------------------------------------------------- /examples/mechanics/plate_with_hole.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate a square plate under tension with a circular hole at its center. 22 | void plateWithHoleExample( const std::string filename ) 23 | { 24 | // ==================================================== 25 | // Choose Kokkos spaces 26 | // ==================================================== 27 | using exec_space = Kokkos::DefaultExecutionSpace; 28 | using memory_space = typename exec_space::memory_space; 29 | 30 | // ==================================================== 31 | // Read inputs 32 | // ==================================================== 33 | CabanaPD::Inputs inputs( filename ); 34 | 35 | // ==================================================== 36 | // Material parameters 37 | // ==================================================== 38 | double rho0 = inputs["density"]; 39 | double E = inputs["elastic_modulus"]; 40 | double nu = 1.0 / 3.0; 41 | double K = E / ( 3 * ( 1 - 2 * nu ) ); 42 | double G0 = inputs["fracture_energy"]; 43 | // double G = E / ( 2.0 * ( 1.0 + nu ) ); // Only for LPS. 44 | double horizon = inputs["horizon"]; 45 | horizon += 1e-10; 46 | 47 | // ==================================================== 48 | // Discretization 49 | // ==================================================== 50 | std::array low_corner = inputs["low_corner"]; 51 | std::array high_corner = inputs["high_corner"]; 52 | std::array num_cells = inputs["num_cells"]; 53 | int m = std::floor( horizon / 54 | ( ( high_corner[0] - low_corner[0] ) / num_cells[0] ) ); 55 | int halo_width = m + 1; // Just to be safe. 56 | 57 | // ==================================================== 58 | // Force model 59 | // ==================================================== 60 | using model_type = CabanaPD::PMB; 61 | CabanaPD::ForceModel force_model( model_type{}, horizon, K, G0 ); 62 | 63 | // ==================================================== 64 | // Custom particle generation and initialization 65 | // ==================================================== 66 | double x_center = 0.5 * ( low_corner[0] + high_corner[0] ); 67 | double y_center = 0.5 * ( low_corner[1] + high_corner[1] ); 68 | double R = inputs["hole_radius"]; 69 | 70 | // Do not create particles inside given cylindrical region 71 | auto init_op = KOKKOS_LAMBDA( const int, const double x[3] ) 72 | { 73 | double rsq = ( x[0] - x_center ) * ( x[0] - x_center ) + 74 | ( x[1] - y_center ) * ( x[1] - y_center ); 75 | if ( rsq < R * R ) 76 | return false; 77 | return true; 78 | }; 79 | 80 | CabanaPD::Particles particles( memory_space{}, model_type{}, 81 | CabanaPD::EnergyStressOutput{} ); 82 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 83 | particles.create( exec_space{}, Cabana::InitUniform{}, init_op ); 84 | 85 | // ==================================================== 86 | // Boundary conditions planes 87 | // ==================================================== 88 | double dx = particles.dx[1]; 89 | CabanaPD::Region plane1( 90 | low_corner[0] - dx, low_corner[0] + dx, low_corner[1], high_corner[1], 91 | low_corner[2], high_corner[2] ); 92 | CabanaPD::Region plane2( 93 | high_corner[0] - dx, high_corner[0] + dx, low_corner[1], high_corner[1], 94 | low_corner[2], high_corner[2] ); 95 | 96 | // ==================================================== 97 | // Custom particle initialization 98 | // ==================================================== 99 | auto rho = particles.sliceDensity(); 100 | auto x = particles.sliceReferencePosition(); 101 | auto f = particles.sliceForce(); 102 | auto nofail = particles.sliceNoFail(); 103 | 104 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 105 | { 106 | // Density 107 | rho( pid ) = rho0; 108 | // No-fail zone 109 | if ( x( pid, 0 ) <= plane1.low[0] + horizon + 1e-10 || 110 | x( pid, 0 ) >= plane2.high[0] - horizon - 1e-10 ) 111 | nofail( pid ) = 1; 112 | }; 113 | particles.update( exec_space{}, init_functor ); 114 | 115 | // ==================================================== 116 | // Create solver 117 | // ==================================================== 118 | CabanaPD::Solver solver( inputs, particles, force_model ); 119 | 120 | // ==================================================== 121 | // Boundary conditions 122 | // ==================================================== 123 | // Create BC last to ensure ghost particles are included. 124 | double sigma0 = inputs["traction"]; 125 | double b0 = sigma0 / dx; 126 | f = solver.particles.sliceForce(); 127 | x = solver.particles.sliceReferencePosition(); 128 | // Create a symmetric force BC in the x-direction. 129 | auto bc_op = KOKKOS_LAMBDA( const int pid, const double ) 130 | { 131 | auto xpos = x( pid, 0 ); 132 | auto sign = std::abs( xpos ) / xpos; 133 | f( pid, 0 ) += b0 * sign; 134 | }; 135 | auto bc = createBoundaryCondition( bc_op, exec_space{}, solver.particles, 136 | true, plane1, plane2 ); 137 | 138 | // ==================================================== 139 | // Simulation run 140 | // ==================================================== 141 | solver.init( bc ); 142 | solver.run( bc ); 143 | } 144 | 145 | // Initialize MPI+Kokkos. 146 | int main( int argc, char* argv[] ) 147 | { 148 | MPI_Init( &argc, &argv ); 149 | Kokkos::initialize( argc, argv ); 150 | 151 | plateWithHoleExample( argv[1] ); 152 | 153 | Kokkos::finalize(); 154 | MPI_Finalize(); 155 | } 156 | -------------------------------------------------------------------------------- /examples/thermomechanics/thermal_deformation_heat_transfer.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate heat transfer in a pseudo-1d cube. 22 | void thermalDeformationHeatTransferExample( const std::string filename ) 23 | { 24 | // ==================================================== 25 | // Choose Kokkos spaces 26 | // ==================================================== 27 | using exec_space = Kokkos::DefaultExecutionSpace; 28 | using memory_space = typename exec_space::memory_space; 29 | 30 | // ==================================================== 31 | // Read inputs 32 | // ==================================================== 33 | CabanaPD::Inputs inputs( filename ); 34 | 35 | // ==================================================== 36 | // Material and problem parameters 37 | // ==================================================== 38 | // Material parameters 39 | double rho0 = inputs["density"]; 40 | double E = inputs["elastic_modulus"]; 41 | double nu = 0.25; 42 | double K = E / ( 3 * ( 1 - 2 * nu ) ); 43 | double horizon = inputs["horizon"]; 44 | horizon += 1e-10; 45 | double alpha = inputs["thermal_expansion_coeff"]; 46 | double kappa = inputs["thermal_conductivity"]; 47 | double cp = inputs["specific_heat_capacity"]; 48 | 49 | // Problem parameters 50 | double temp0 = inputs["reference_temperature"]; 51 | 52 | // ==================================================== 53 | // Discretization 54 | // ==================================================== 55 | std::array low_corner = inputs["low_corner"]; 56 | std::array high_corner = inputs["high_corner"]; 57 | std::array num_cells = inputs["num_cells"]; 58 | int m = std::floor( horizon / 59 | ( ( high_corner[0] - low_corner[0] ) / num_cells[0] ) ); 60 | int halo_width = m + 1; // Just to be safe. 61 | 62 | // ==================================================== 63 | // Force model type 64 | // ==================================================== 65 | using model_type = CabanaPD::PMB; 66 | using thermal_type = CabanaPD::DynamicTemperature; 67 | 68 | // ==================================================== 69 | // Particle generation 70 | // ==================================================== 71 | // Does not set displacements, velocities, etc. 72 | CabanaPD::Particles particles( memory_space{}, model_type{}, 73 | thermal_type{} ); 74 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 75 | particles.create( exec_space{} ); 76 | 77 | // ==================================================== 78 | // Custom particle initialization 79 | // ==================================================== 80 | auto rho = particles.sliceDensity(); 81 | auto temp = particles.sliceTemperature(); 82 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 83 | { 84 | // Density 85 | rho( pid ) = rho0; 86 | // Temperature 87 | temp( pid ) = temp0; 88 | }; 89 | particles.update( exec_space{}, init_functor ); 90 | 91 | // ==================================================== 92 | // Force model 93 | // ==================================================== 94 | CabanaPD::ForceModel force_model( model_type{}, CabanaPD::NoFracture{}, 95 | horizon, K, temp, kappa, cp, alpha, 96 | temp0 ); 97 | 98 | // ==================================================== 99 | // Create solver 100 | // ==================================================== 101 | CabanaPD::Solver solver( inputs, particles, force_model ); 102 | 103 | // ==================================================== 104 | // Boundary condition 105 | // ==================================================== 106 | // Temperature profile imposed on top and bottom surfaces 107 | double dy = solver.particles.dx[1]; 108 | using plane_type = CabanaPD::Region; 109 | 110 | // Top surface 111 | plane_type plane1( low_corner[0], high_corner[0], high_corner[1] - dy, 112 | high_corner[1] + dy, low_corner[2], high_corner[2] ); 113 | 114 | // Bottom surface 115 | plane_type plane2( low_corner[0], high_corner[0], low_corner[1] - dy, 116 | low_corner[1] + dy, low_corner[2], high_corner[2] ); 117 | 118 | // This is purposely delayed until after solver init so that ghosted 119 | // particles are correctly taken into account for lambda capture here. 120 | temp = solver.particles.sliceTemperature(); 121 | auto temp_bc = KOKKOS_LAMBDA( const int pid, const double ) 122 | { 123 | temp( pid ) = 0.0; 124 | }; 125 | 126 | auto bc = CabanaPD::createBoundaryCondition( 127 | temp_bc, exec_space{}, solver.particles, false, plane1, plane2 ); 128 | 129 | // ==================================================== 130 | // Simulation run 131 | // ==================================================== 132 | solver.init( bc ); 133 | solver.run( bc ); 134 | 135 | // ==================================================== 136 | // Outputs 137 | // ==================================================== 138 | // Output temperature along the y-axis 139 | int profile_dim = 1; 140 | auto value = KOKKOS_LAMBDA( const int pid ) { return temp( pid ); }; 141 | std::string file_name = "temperature_yaxis_profile.txt"; 142 | createOutputProfile( file_name, solver.particles, profile_dim, value ); 143 | } 144 | 145 | // Initialize MPI+Kokkos. 146 | int main( int argc, char* argv[] ) 147 | { 148 | MPI_Init( &argc, &argv ); 149 | Kokkos::initialize( argc, argv ); 150 | 151 | thermalDeformationHeatTransferExample( argv[1] ); 152 | 153 | Kokkos::finalize(); 154 | MPI_Finalize(); 155 | } 156 | -------------------------------------------------------------------------------- /src/force/CabanaPD_Contact.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef CONTACT_H 13 | #define CONTACT_H 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace CabanaPD 25 | { 26 | /****************************************************************************** 27 | Contact helper functions 28 | ******************************************************************************/ 29 | template 30 | KOKKOS_INLINE_FUNCTION void 31 | getRelativeVelocityComponents( const VelType& vel, const int i, const int j, 32 | double& vx, double& vy, double& vz ) 33 | { 34 | vx = vel( j, 0 ) - vel( i, 0 ); 35 | vy = vel( j, 1 ) - vel( i, 1 ); 36 | vz = vel( j, 2 ) - vel( i, 2 ); 37 | }; 38 | 39 | /****************************************************************************** 40 | Normal repulsion forces 41 | ******************************************************************************/ 42 | template 43 | class Force 44 | : public BaseForce 45 | { 46 | public: 47 | using base_type = BaseForce; 48 | Force() = default; 49 | 50 | template 52 | void computeForceFull( const ModelType& model, ForceType& fc, 53 | const PosType& x, const PosType& u, 54 | ParticleType& particles, NeighborType& neighbor ) 55 | { 56 | const auto vol = particles.sliceVolume(); 57 | 58 | neighbor.update( particles, model ); 59 | 60 | auto contact_full = KOKKOS_LAMBDA( const int i, const int j ) 61 | { 62 | double fcx_i = 0.0; 63 | double fcy_i = 0.0; 64 | double fcz_i = 0.0; 65 | 66 | double xi, r, s; 67 | double rx, ry, rz; 68 | getDistance( x, u, i, j, xi, r, s, rx, ry, rz ); 69 | 70 | if ( r < model.radius ) 71 | { 72 | const double coeff = model.forceCoeff( r, vol( j ) ); 73 | fcx_i = coeff * rx / r; 74 | fcy_i = coeff * ry / r; 75 | fcz_i = coeff * rz / r; 76 | } 77 | fc( i, 0 ) += fcx_i; 78 | fc( i, 1 ) += fcy_i; 79 | fc( i, 2 ) += fcz_i; 80 | }; 81 | 82 | _timer.start(); 83 | 84 | // FIXME: using default space for now. 85 | using exec_space = typename MemorySpace::execution_space; 86 | neighbor.iterate( exec_space{}, contact_full, particles, 87 | "CabanaPD::Contact::compute_full" ); 88 | Kokkos::fence(); 89 | 90 | _timer.stop(); 91 | } 92 | 93 | // FIXME: implement energy 94 | template 96 | void computeEnergyFull( const ModelType&, WType&, const PosType&, 97 | const PosType&, ParticleType&, const int, 98 | NeighborType& ) 99 | { 100 | } 101 | 102 | protected: 103 | using base_type::_timer; 104 | }; 105 | 106 | /****************************************************************************** 107 | Hertzian contact forces 108 | ******************************************************************************/ 109 | template 110 | class Force 111 | : public BaseForce 112 | { 113 | public: 114 | using base_type = BaseForce; 115 | Force() = default; 116 | 117 | template 119 | void computeForceFull( const ModelType& model, ForceType& fc, 120 | const PosType& x, const PosType& u, 121 | const ParticleType& particles, 122 | NeighborType& neighbor ) 123 | { 124 | const auto vol = particles.sliceVolume(); 125 | const auto rho = particles.sliceDensity(); 126 | const auto vel = particles.sliceVelocity(); 127 | 128 | neighbor.update( particles, model ); 129 | 130 | auto contact_full = KOKKOS_LAMBDA( const int i, const int j ) 131 | { 132 | double xi, r, s; 133 | double rx, ry, rz; 134 | getDistance( x, u, i, j, xi, r, s, rx, ry, rz ); 135 | 136 | double nx, ny, nz; 137 | nx = rx / r; 138 | ny = ry / r; 139 | nz = rz / r; 140 | 141 | double vx, vy, vz; 142 | getRelativeVelocityComponents( vel, i, j, vx, vy, vz ); 143 | 144 | double vn = vx * nx + vy * ny + vz * nz; 145 | 146 | double fn = model.normalForce( r, vn, vol( i ), rho( i ) ); 147 | fc( i, 0 ) += fn * nx; 148 | fc( i, 1 ) += fn * ny; 149 | fc( i, 2 ) += fn * nz; 150 | 151 | double vtx, vty, vtz; 152 | vtx = vx - vn * nx; 153 | vty = vy - vn * ny; 154 | vtz = vz - vn * nz; 155 | 156 | double ftx = 0.0, fty = 0.0, ftz = 0.0; 157 | model.tangentialForce( vtx, vty, vtz, fn, ftx, fty, ftz ); 158 | fc( i, 0 ) += ftx; 159 | fc( i, 1 ) += fty; 160 | fc( i, 2 ) += ftz; 161 | }; 162 | 163 | _timer.start(); 164 | 165 | // FIXME: using default space for now. 166 | using exec_space = typename MemorySpace::execution_space; 167 | neighbor.iterate( exec_space{}, contact_full, particles, 168 | "CabanaPD::Contact::compute_full" ); 169 | Kokkos::fence(); 170 | 171 | _timer.stop(); 172 | } 173 | 174 | // FIXME: implement energy 175 | template 177 | void computeEnergyFull( const ModelType&, WType&, const PosType&, 178 | const PosType&, ParticleType&, ParallelType& ) 179 | { 180 | } 181 | 182 | protected: 183 | using base_type::_timer; 184 | using base_type::_total_strain_energy; 185 | }; 186 | 187 | } // namespace CabanaPD 188 | 189 | #endif 190 | -------------------------------------------------------------------------------- /src/force_models/CabanaPD_HertzianJKR.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef CONTACTMODEL_HERTZIANJKR_H 13 | #define CONTACTMODEL_HERTZIANJKR_H 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace CabanaPD 22 | { 23 | struct HertzianJKRModel : public ContactModel 24 | { 25 | using base_type = ContactModel; 26 | using fracture_type = NoFracture; 27 | using thermal_type = TemperatureIndependent; 28 | // Tag to dispatch to force iteration. 29 | using force_tag = HertzianModel; 30 | 31 | using base_type::radius; 32 | double nu; // Poisson's ratio 33 | double E; // Young's modulus 34 | double mu; // Coloumb friction coefficient 35 | 36 | double Rs; // Equivalent radius 37 | double Es; // Equivalent Young's modulus 38 | double Gs; // Equivalent shear modulus 39 | 40 | double e; // Coefficient of restitution 41 | double beta; // Damping coefficient 42 | double coeff_h_n; 43 | double coeff_h_d; 44 | 45 | double Gamma; // Surface energy 46 | double a0; // Equilibrium contact radius 47 | double delta_tear; // Maximum separation distance to break contact 48 | double fc; // Maximum cohesion (pull-off) force 49 | 50 | HertzianJKRModel() {} 51 | HertzianJKRModel( const double _radius, const double _extend, 52 | const double _nu, const double _E, const double _e, 53 | const double _gamma, const double _mu ) 54 | : base_type( _radius, _extend ) 55 | , nu( _nu ) 56 | , E( _E ) 57 | , mu( _mu ) 58 | { 59 | Rs = 0.5 * radius; 60 | Es = _E / ( 2.0 * ( 1.0 - Kokkos::pow( nu, 2.0 ) ) ); 61 | Gs = _E / ( 4.0 * ( 2 - nu ) * ( 1 + nu ) ); 62 | e = _e; 63 | double ln_e = Kokkos::log( e ); 64 | beta = -ln_e / Kokkos::sqrt( Kokkos::pow( ln_e, 2.0 ) + 65 | Kokkos::pow( pi, 2.0 ) ); 66 | 67 | // Derived constants. 68 | coeff_h_n = 4.0 / 3.0 * Es / Rs; 69 | coeff_h_d = -2.0 * Kokkos::sqrt( 5.0 / 6.0 ) * beta; 70 | 71 | // JKR cohesion model 72 | Gamma = 2.0 * _gamma; 73 | a0 = Kokkos::pow( 9.0 / 2.0 * Gamma * pi / Es * Kokkos::pow( Rs, 2.0 ), 74 | 1.0 / 3.0 ); 75 | 76 | delta_tear = -1.0 / 2.0 * Kokkos::pow( 6.0, -1.0 / 3.0 ) * 77 | Kokkos::pow( a0, 2.0 ) / Rs; 78 | 79 | fc = 3.0 / 2.0 * pi * Rs * Gamma; 80 | } 81 | 82 | KOKKOS_INLINE_FUNCTION 83 | auto normalForce( const double r, const double vn, const double vol, 84 | const double rho ) const 85 | { 86 | // Contact "overlap" 87 | const double delta_n = ( 2.0 * radius - r ); 88 | 89 | // HertzJKR normal force coefficient 90 | double coeff = 0.0; 91 | if ( delta_n >= delta_tear ) 92 | { 93 | auto a = patchRadius( delta_n ); 94 | auto a3 = Kokkos::pow( a, 3.0 ); 95 | coeff = 96 | -1.0 * ( coeff_h_n * a3 - 97 | Kokkos::pow( 8.0 * Gamma * pi * Es * a3, 1.0 / 2.0 ) ); 98 | 99 | // Damping force coefficient 100 | double Sn = 2.0 * Es * a; 101 | double ms = ( rho * vol ) / 2.0; 102 | 103 | coeff -= coeff_h_d * Kokkos::sqrt( Sn * ms ) * vn; 104 | 105 | if ( delta_n <= 0.0 && vn <= 0.0 ) 106 | { 107 | coeff = 0.0; 108 | } 109 | } 110 | coeff /= vol; 111 | 112 | return coeff; 113 | } 114 | KOKKOS_INLINE_FUNCTION 115 | void tangentialForce( const double vtx, const double vty, const double vtz, 116 | const double fn, double& ftx, double& fty, 117 | double& ftz ) const 118 | { 119 | // Coloumb sliding (kinetic) friction 120 | auto mu_c = [&]( const double v ) 121 | { return Kokkos::abs( mu * Kokkos::tanh( 10 * v ) ); }; 122 | 123 | auto vt = Kokkos::hypot( vtx, vty, vtz ); 124 | auto fna = Kokkos::abs( fn ); 125 | 126 | ftx = ( vt > 0 ) ? mu_c( vtx ) * fna * vtx / vt : 0.0; 127 | fty = ( vt > 0 ) ? mu_c( vty ) * fna * vty / vt : 0.0; 128 | ftz = ( vt > 0 ) ? mu_c( vtz ) * fna * vtz / vt : 0.0; 129 | } 130 | 131 | // The normal force for JKR theory is given in terms of the contact 132 | // patch radius, (a), which obeys a different relationship with the 133 | // normal contact overlap, (delta_n), than from ordinary Hertz theory 134 | // alone. 135 | // 136 | // We need to calculate (a) from the more readily available (delta_n) 137 | // Here we use the approach from PFC: 138 | // https://docs.itascacg.com/pfc700/common/contactmodel/jkr/doc/manual/cmjkr.html 139 | // which is itself taken from E. Parteli (2014). 140 | 141 | KOKKOS_INLINE_FUNCTION 142 | double patchRadius( const double delta_n ) const 143 | { 144 | double c0 = Kokkos::pow( Rs * delta_n, 2.0 ); 145 | double c1 = -4.0 * ( 1.0 - Kokkos::pow( nu, 2.0 ) ) * pi * Gamma * 146 | Kokkos::pow( Rs, 2.0 ) / E; 147 | double c2 = -2.0 * Rs * delta_n; 148 | double P = -Kokkos::pow( c2, 2.0 ) / 12.0 - c0; 149 | double Q = -Kokkos::pow( c2, 3.0 ) / 108.0 + c0 * c2 / 3.0 - 150 | Kokkos::pow( c1, 2.0 ) / 8.0; 151 | double U = Kokkos::pow( 152 | -Q / 2.0 + Kokkos::pow( 153 | Kokkos::max( 0.0, Kokkos::pow( Q, 2.0 ) / 4.0 + 154 | Kokkos::pow( P, 3.0 ) / 27.0 ), 155 | 1.0 / 2.0 ), 156 | 1.0 / 3.0 ); 157 | 158 | double s = 0.0; 159 | if ( P != 0.0 ) 160 | { 161 | s = -5.0 / 6.0 * c2 + U - P / ( 3.0 * U ); 162 | } 163 | else 164 | { 165 | s = -5.0 / 6.0 * c2 - Kokkos::pow( Q, 1.0 / 3.0 ); 166 | } 167 | double w = Kokkos::pow( Kokkos::max( 0.0, c2 + 2.0 * s ), 1.0 / 2.0 ); 168 | double lambda = c1 / ( 2.0 * w + 1e-14 ); 169 | 170 | // The final value for (a) 171 | return 1.0 / 2.0 * 172 | ( w + 173 | Kokkos::pow( Kokkos::max( 0.0, Kokkos::pow( w, 2.0 ) - 174 | 4.0 * ( c2 + s + lambda ) ), 175 | 1.0 / 2.0 ) ); 176 | } 177 | }; 178 | 179 | } // namespace CabanaPD 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /src/CabanaPD_OutputProfiles.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef DISPLACEMENTPROFILE_H 13 | #define DISPLACEMENTPROFILE_H 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | namespace CabanaPD 23 | { 24 | 25 | template 26 | void createOutputProfile( std::string file_name, ParticleType particles, 27 | const int profile_dim, UserFunctor user ) 28 | { 29 | using memory_space = typename ParticleType::memory_space; 30 | using exec_space = typename memory_space::execution_space; 31 | 32 | Region line( profile_dim, particles.dx ); 33 | ParticleSteeringVector sv( exec_space{}, particles, line ); 34 | 35 | auto profile = Kokkos::View( 36 | Kokkos::ViewAllocateWithoutInitializing( "output_profile" ), 37 | sv.size() ); 38 | auto indices = sv._view; 39 | auto x = particles.sliceReferencePosition(); 40 | // FIXME: not in order. 41 | auto measure_profile = KOKKOS_LAMBDA( const int b ) 42 | { 43 | auto p = indices( b ); 44 | profile( b, 0 ) = x( p, profile_dim ); 45 | profile( b, 1 ) = user( p ); 46 | }; 47 | Kokkos::RangePolicy policy( 48 | 0, indices.size() ); 49 | Kokkos::parallel_for( "output_profile", policy, measure_profile ); 50 | Kokkos::fence(); 51 | auto profile_host = 52 | Kokkos::create_mirror_view_and_copy( Kokkos::HostSpace{}, profile ); 53 | 54 | std::fstream fout; 55 | fout.open( file_name, std::ios::app ); 56 | auto mpi_rank = particles.rank(); 57 | for ( std::size_t p = 0; p < indices.size(); p++ ) 58 | { 59 | fout << mpi_rank << " " << profile_host( p, 0 ) << " " 60 | << profile_host( p, 1 ) << std::endl; 61 | } 62 | } 63 | 64 | template 65 | void createDisplacementProfile( std::string file_name, ParticleType particles, 66 | const int profile_dim, 67 | int displacement_dim = -1 ) 68 | { 69 | if ( displacement_dim == -1 ) 70 | displacement_dim = profile_dim; 71 | 72 | auto u = particles.sliceDisplacement(); 73 | auto value = KOKKOS_LAMBDA( const int pid ) 74 | { 75 | return u( pid, displacement_dim ); 76 | }; 77 | createOutputProfile( file_name, particles, profile_dim, value ); 78 | } 79 | 80 | template 81 | void createDisplacementMagnitudeProfile( std::string file_name, 82 | ParticleType particles, 83 | const int profile_dim ) 84 | { 85 | auto u = particles.sliceDisplacement(); 86 | auto magnitude = KOKKOS_LAMBDA( const int pid ) 87 | { 88 | return Kokkos::sqrt( u( pid, 0 ) * u( pid, 0 ) + 89 | u( pid, 1 ) * u( pid, 1 ) + 90 | u( pid, 2 ) * u( pid, 2 ) ); 91 | }; 92 | createOutputProfile( file_name, particles, profile_dim, magnitude ); 93 | } 94 | 95 | /****************************************************************************** 96 | Scalar time series 97 | ******************************************************************************/ 98 | template 99 | class OutputTimeSeries 100 | { 101 | using memory_space = MemorySpace; 102 | using profile_type = Kokkos::View; 103 | 104 | using steering_vector_type = ParticleSteeringVector; 105 | steering_vector_type _indices; 106 | 107 | std::string file_name; 108 | profile_type _profile; 109 | FunctorType _output; 110 | std::size_t index; 111 | 112 | public: 113 | OutputTimeSeries( std::string name, Inputs inputs, 114 | const steering_vector_type indices, FunctorType output ) 115 | : _indices( indices ) 116 | , file_name( name ) 117 | , _output( output ) 118 | , index( 0 ) 119 | { 120 | double time = inputs["final_time"]; 121 | double dt = inputs["timestep"]; 122 | double freq = inputs["output_frequency"]; 123 | int output_steps = static_cast( time / dt / freq ); 124 | // Purposely using zero-init here. 125 | _profile = profile_type( "time_output", output_steps ); 126 | } 127 | 128 | void update() 129 | { 130 | Kokkos::RangePolicy policy( 131 | 0, _indices.size() ); 132 | // Local copies for lambda. Avoiding functor due to string name member. 133 | auto indices = _indices; 134 | auto output = _output; 135 | // Reduce into host view. 136 | Kokkos::parallel_reduce( 137 | "time_series", policy, 138 | KOKKOS_LAMBDA( const int b, double& px ) { 139 | auto p = indices._view( b ); 140 | px += output( p ); 141 | }, 142 | _profile( index ) ); 143 | Kokkos::fence(); 144 | index++; 145 | } 146 | 147 | void print( MPI_Comm comm ) 148 | { 149 | auto profile_host = Kokkos::create_mirror_view_and_copy( 150 | Kokkos::HostSpace{}, _profile ); 151 | MPI_Allreduce( MPI_IN_PLACE, profile_host.data(), profile_host.size(), 152 | MPI_DOUBLE, MPI_SUM, comm ); 153 | auto num_particles = static_cast( _indices.size() ); 154 | MPI_Allreduce( MPI_IN_PLACE, &num_particles, 1, MPI_INT, MPI_SUM, 155 | comm ); 156 | 157 | if ( print_rank() ) 158 | { 159 | std::fstream fout; 160 | fout.open( file_name, std::ios::app ); 161 | for ( std::size_t t = 0; t < index; t++ ) 162 | { 163 | fout << std::fixed << std::setprecision( 15 ) 164 | << profile_host( t ) << " " 165 | << profile_host( t ) / num_particles << std::endl; 166 | } 167 | } 168 | } 169 | }; 170 | 171 | template 173 | auto createOutputTimeSeries( std::string name, const Inputs inputs, 174 | ExecSpace exec_space, 175 | const ParticleType& particles, FunctorType user, 176 | const GeometryType geom ) 177 | { 178 | ParticleSteeringVector indices( exec_space, particles, geom ); 179 | return OutputTimeSeries( name, inputs, indices, user ); 180 | } 181 | 182 | } // namespace CabanaPD 183 | 184 | #endif 185 | -------------------------------------------------------------------------------- /examples/thermomechanics/thermal_crack.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate crack initiation and propagation in a ceramic plate under thermal 22 | // shock caused by water quenching. 23 | void thermalCrackExample( const std::string filename ) 24 | { 25 | // ==================================================== 26 | // Choose Kokkos spaces 27 | // ==================================================== 28 | using exec_space = Kokkos::DefaultExecutionSpace; 29 | using memory_space = typename exec_space::memory_space; 30 | 31 | // ==================================================== 32 | // Read inputs 33 | // ==================================================== 34 | CabanaPD::Inputs inputs( filename ); 35 | 36 | // ==================================================== 37 | // Material and problem parameters 38 | // ==================================================== 39 | // Material parameters 40 | double rho0 = inputs["density"]; 41 | double E = inputs["elastic_modulus"]; 42 | double nu = 0.25; 43 | double K = E / ( 3 * ( 1 - 2 * nu ) ); 44 | double G0 = inputs["fracture_energy"]; 45 | double horizon = inputs["horizon"]; 46 | horizon += 1e-10; 47 | double alpha = inputs["thermal_expansion_coeff"]; 48 | 49 | // Problem parameters 50 | double temp0 = inputs["reference_temperature"]; 51 | double temp_w = inputs["background_temperature"]; 52 | double t_ramp = inputs["surface_temperature_ramp_time"]; 53 | 54 | // ==================================================== 55 | // Discretization 56 | // ==================================================== 57 | std::array low_corner = inputs["low_corner"]; 58 | std::array high_corner = inputs["high_corner"]; 59 | std::array num_cells = inputs["num_cells"]; 60 | int m = std::floor( horizon / 61 | ( ( high_corner[0] - low_corner[0] ) / num_cells[0] ) ); 62 | int halo_width = m + 1; // Just to be safe. 63 | 64 | // ==================================================== 65 | // Force model type 66 | // ==================================================== 67 | using model_type = CabanaPD::PMB; 68 | using thermal_type = CabanaPD::TemperatureDependent; 69 | 70 | // ==================================================== 71 | // Particle generation 72 | // ==================================================== 73 | // Does not set displacements, velocities, etc. 74 | CabanaPD::Particles particles( memory_space{}, model_type{}, 75 | thermal_type{} ); 76 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 77 | particles.create( exec_space{} ); 78 | 79 | // ==================================================== 80 | // Custom particle initialization 81 | // ==================================================== 82 | auto rho = particles.sliceDensity(); 83 | auto init_functor = KOKKOS_LAMBDA( const int pid ) { rho( pid ) = rho0; }; 84 | particles.update( exec_space{}, init_functor ); 85 | 86 | // ==================================================== 87 | // Force model 88 | // ==================================================== 89 | auto temp = particles.sliceTemperature(); 90 | CabanaPD::ForceModel force_model( model_type{}, horizon, K, G0, temp, alpha, 91 | temp0 ); 92 | 93 | // ==================================================== 94 | // Create solver 95 | // ==================================================== 96 | 97 | CabanaPD::Solver solver( inputs, particles, force_model ); 98 | 99 | // -------------------------------------------- 100 | // Thermal shock 101 | // -------------------------------------------- 102 | auto x = solver.particles.sliceReferencePosition(); 103 | temp = solver.particles.sliceTemperature(); 104 | 105 | // Plate limits 106 | double X0 = low_corner[0]; 107 | double Xn = high_corner[0]; 108 | double Y0 = low_corner[1]; 109 | double Yn = high_corner[1]; 110 | // This is purposely delayed until after solver init so that ghosted 111 | // particles are correctly taken into account for lambda capture here. 112 | auto temp_func = KOKKOS_LAMBDA( const int pid, const double t ) 113 | { 114 | // Define a time-dependent surface temperature: 115 | // An inverted triangular pulse over a 2*t_ramp period starting at temp0 116 | // and linearly decreasing to temp_w within t_ramp, then linearly 117 | // increasing back to temp0, and finally staying constant at temp0 118 | double temp_infinity; 119 | if ( t <= t_ramp ) 120 | { 121 | // Increasing pulse 122 | temp_infinity = temp0 - ( temp0 - temp_w ) * ( t / t_ramp ); 123 | } 124 | else if ( t < 2 * t_ramp ) 125 | { 126 | // Decreasing pulse 127 | temp_infinity = 128 | temp_w + ( temp0 - temp_w ) * ( t - t_ramp ) / t_ramp; 129 | } 130 | else 131 | { 132 | // Constant value 133 | temp_infinity = temp0; 134 | } 135 | 136 | // Rescale x and y particle position values 137 | double xi = ( 2.0 * x( pid, 0 ) - ( X0 + Xn ) ) / ( Xn - X0 ); 138 | double eta = ( 2.0 * x( pid, 1 ) - ( Y0 + Yn ) ) / ( Yn - Y0 ); 139 | 140 | // Define profile powers in x- and y-directions 141 | double sx = 1.0 / 50.0; 142 | double sy = 1.0 / 10.0; 143 | 144 | // Define profiles in x- and y-direcions 145 | double fx = 1.0 - Kokkos::pow( Kokkos::abs( xi ), 1.0 / sx ); 146 | double fy = 1.0 - Kokkos::pow( Kokkos::abs( eta ), 1.0 / sy ); 147 | 148 | // Compute particle temperature 149 | temp( pid ) = temp_infinity + ( temp0 - temp_infinity ) * fx * fy; 150 | }; 151 | CabanaPD::BodyTerm body_term( temp_func, solver.particles.size(), false ); 152 | 153 | // ==================================================== 154 | // Simulation run 155 | // ==================================================== 156 | solver.init( body_term ); 157 | solver.run( body_term ); 158 | } 159 | 160 | // Initialize MPI+Kokkos. 161 | int main( int argc, char* argv[] ) 162 | { 163 | MPI_Init( &argc, &argv ); 164 | Kokkos::initialize( argc, argv ); 165 | 166 | thermalCrackExample( argv[1] ); 167 | 168 | Kokkos::finalize(); 169 | MPI_Finalize(); 170 | } 171 | -------------------------------------------------------------------------------- /src/CabanaPD_HeatTransfer.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022-2023 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef HEATTRANSFER_H 13 | #define HEATTRANSFER_H 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace CabanaPD 21 | { 22 | 23 | template 24 | class HeatTransfer; 25 | 26 | // Peridynamic heat transfer with forward-Euler time integration. 27 | // Inherits only because this is a similar neighbor-based kernel. 28 | template 29 | class HeatTransfer 30 | { 31 | public: 32 | // Using the default exec_space. 33 | using exec_space = typename MemorySpace::execution_space; 34 | HeatTransfer() = default; 35 | 36 | protected: 37 | Timer _timer; 38 | Timer _euler_timer; 39 | 40 | public: 41 | template 43 | void computeHeatTransferFull( const ModelType& model, 44 | TemperatureType& conduction, const PosType& x, 45 | const PosType& u, 46 | const ParticleType& particles, 47 | NeighborType& neighbor ) 48 | { 49 | _timer.start(); 50 | 51 | const auto vol = particles.sliceVolume(); 52 | const auto temp = particles.sliceTemperature(); 53 | 54 | auto temp_func = KOKKOS_LAMBDA( const int i, const int j ) 55 | { 56 | double xi, r, s; 57 | getDistance( x, u, i, j, xi, r, s ); 58 | 59 | const double coeff = model.microconductivity_function( xi ); 60 | conduction( i ) += 61 | coeff * ( temp( j ) - temp( i ) ) / xi / xi * vol( j ); 62 | }; 63 | 64 | neighbor.iterate( exec_space{}, temp_func, particles, 65 | "CabanaPD::HeatTransfer::computeFull" ); 66 | Kokkos::fence(); 67 | _timer.stop(); 68 | } 69 | 70 | template 71 | void forwardEuler( const ModelType& model, const ParticleType& particles, 72 | const double dt ) 73 | { 74 | _euler_timer.start(); 75 | const auto rho = particles.sliceDensity(); 76 | const auto conduction = particles.sliceTemperatureConduction(); 77 | auto temp = particles.sliceTemperature(); 78 | auto euler_func = KOKKOS_LAMBDA( const int i ) 79 | { 80 | temp( i ) += dt / rho( i ) / model.cp * conduction( i ); 81 | }; 82 | Kokkos::RangePolicy policy( particles.frozenOffset(), 83 | particles.localOffset() ); 84 | Kokkos::parallel_for( "CabanaPD::HeatTransfer::forwardEuler", policy, 85 | euler_func ); 86 | Kokkos::fence(); 87 | _euler_timer.stop(); 88 | } 89 | }; 90 | 91 | template 92 | class HeatTransfer 93 | : public HeatTransfer 94 | { 95 | public: 96 | // Using the default exec_space. 97 | using exec_space = typename MemorySpace::execution_space; 98 | using base_type = HeatTransfer; 99 | HeatTransfer() = default; 100 | 101 | protected: 102 | using base_type::_euler_timer; 103 | using base_type::_timer; 104 | 105 | public: 106 | template 108 | void computeHeatTransferFull( const ModelType& model, 109 | TemperatureType& conduction, const PosType& x, 110 | const PosType& u, 111 | const ParticleType& particles, 112 | NeighborType& neighbor ) 113 | { 114 | _timer.start(); 115 | using neighbor_list_type = typename NeighborType::list_type; 116 | auto neigh_list = neighbor.list(); 117 | const auto mu = neighbor.brokenBonds(); 118 | 119 | const auto vol = particles.sliceVolume(); 120 | const auto temp = particles.sliceTemperature(); 121 | 122 | auto temp_func = KOKKOS_LAMBDA( const int i ) 123 | { 124 | std::size_t num_neighbors = 125 | Cabana::NeighborList::numNeighbor( 126 | neigh_list, i ); 127 | for ( std::size_t n = 0; n < num_neighbors; n++ ) 128 | { 129 | std::size_t j = 130 | Cabana::NeighborList::getNeighbor( 131 | neigh_list, i, n ); 132 | 133 | // Get the reference positions and displacements. 134 | double xi, r, s; 135 | getDistance( x, u, i, j, xi, r, s ); 136 | 137 | // Only include unbroken bonds. 138 | if ( mu( i, n ) > 0 ) 139 | { 140 | const double coeff = model.microconductivity_function( xi ); 141 | conduction( i ) += 142 | coeff * ( temp( j ) - temp( i ) ) / xi / xi * vol( j ); 143 | } 144 | } 145 | }; 146 | 147 | neighbor.iterateLinear( exec_space{}, temp_func, particles, 148 | "CabanaPD::HeatTransfer::computeFull" ); 149 | Kokkos::fence(); 150 | _timer.stop(); 151 | } 152 | }; 153 | 154 | // Heat transfer free functions. 155 | template 157 | void computeHeatTransfer( const ModelType& model, 158 | HeatTransferType& heat_transfer, 159 | ParticleType& particles, const NeighborType& neighbor, 160 | const double dt ) 161 | { 162 | auto x = particles.sliceReferencePosition(); 163 | auto u = particles.sliceDisplacement(); 164 | auto conduction = particles.sliceTemperatureConduction(); 165 | auto conduction_a = particles.sliceTemperatureConductionAtomic(); 166 | 167 | // Reset temperature conduction. 168 | Cabana::deep_copy( conduction, 0.0 ); 169 | 170 | // Temperature only needs to be atomic if using team threading. 171 | if ( std::is_same::value ) 172 | heat_transfer.computeHeatTransferFull( model, conduction_a, x, u, 173 | particles, neighbor ); 174 | else 175 | heat_transfer.computeHeatTransferFull( model, conduction, x, u, 176 | particles, neighbor ); 177 | Kokkos::fence(); 178 | 179 | heat_transfer.forwardEuler( model, particles, dt ); 180 | } 181 | 182 | } // namespace CabanaPD 183 | 184 | #endif 185 | -------------------------------------------------------------------------------- /src/CabanaPD_Boundary.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #ifndef BOUNDARYCONDITION_H 13 | #define BOUNDARYCONDITION_H 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | namespace CabanaPD 24 | { 25 | 26 | struct ForceValueBCTag 27 | { 28 | }; 29 | struct ForceUpdateBCTag 30 | { 31 | }; 32 | 33 | // Custom boundary condition. 34 | template 35 | struct BoundaryCondition 36 | { 37 | using steering_vector_type = ParticleSteeringVector; 38 | steering_vector_type _indices; 39 | UserFunctor _user_functor; 40 | bool _force_update; 41 | 42 | Timer _timer; 43 | 44 | BoundaryCondition( steering_vector_type bc_indices, UserFunctor user, 45 | const bool force ) 46 | : _indices( bc_indices ) 47 | , _user_functor( user ) 48 | , _force_update( force ) 49 | { 50 | } 51 | 52 | template 53 | void apply( ExecSpace, ParticleType& particles, double time ) 54 | { 55 | checkParticleCount( _indices.particle_count, 56 | particles.referenceOffset(), "BoundaryCondition" ); 57 | _timer.start(); 58 | 59 | auto user = _user_functor; 60 | auto index_space = _indices._view; 61 | Kokkos::RangePolicy policy( 0, index_space.size() ); 62 | Kokkos::parallel_for( 63 | "CabanaPD::BC::apply", policy, KOKKOS_LAMBDA( const int b ) { 64 | auto pid = index_space( b ); 65 | user( pid, time ); 66 | } ); 67 | Kokkos::fence(); 68 | _timer.stop(); 69 | } 70 | 71 | auto forceUpdate() { return _force_update; } 72 | 73 | auto time() { return _timer.time(); }; 74 | auto timeInit() { return _indices.time(); }; 75 | }; 76 | 77 | template 78 | struct BoundaryCondition 79 | { 80 | double _value; 81 | using steering_vector_type = ParticleSteeringVector; 82 | steering_vector_type _indices; 83 | const bool _force_update = true; 84 | 85 | Timer _timer; 86 | 87 | BoundaryCondition( ForceValueBCTag, const double value, 88 | steering_vector_type bc_indices ) 89 | : _value( value ) 90 | , _indices( bc_indices ) 91 | { 92 | } 93 | 94 | template 95 | void apply( ExecSpace, ParticleType& particles, double ) 96 | { 97 | checkParticleCount( _indices.particle_count, 98 | particles.referenceOffset(), "BoundaryCondition" ); 99 | 100 | _timer.start(); 101 | auto f = particles.sliceForce(); 102 | auto index_space = _indices._view; 103 | Kokkos::RangePolicy policy( 0, index_space.size() ); 104 | auto value = _value; 105 | Kokkos::parallel_for( 106 | "CabanaPD::BC::apply", policy, KOKKOS_LAMBDA( const int b ) { 107 | auto pid = index_space( b ); 108 | for ( int d = 0; d < 3; d++ ) 109 | f( pid, d ) = value; 110 | } ); 111 | Kokkos::fence(); 112 | _timer.stop(); 113 | } 114 | 115 | auto forceUpdate() { return _force_update; } 116 | 117 | auto time() { return _timer.time(); }; 118 | auto timeInit() { return _indices.time(); }; 119 | }; 120 | 121 | template 122 | struct BoundaryCondition 123 | { 124 | double _value; 125 | using steering_vector_type = ParticleSteeringVector; 126 | steering_vector_type _indices; 127 | const bool _force_update = true; 128 | 129 | Timer _timer; 130 | 131 | BoundaryCondition( ForceUpdateBCTag, const double value, 132 | steering_vector_type bc_indices ) 133 | : _value( value ) 134 | , _indices( bc_indices ) 135 | { 136 | } 137 | 138 | template 139 | void apply( ExecSpace, ParticleType& particles, double ) 140 | { 141 | checkParticleCount( _indices.particle_count, 142 | particles.referenceOffset(), "BoundaryCondition" ); 143 | 144 | _timer.start(); 145 | auto f = particles.sliceForce(); 146 | auto index_space = _indices._view; 147 | Kokkos::RangePolicy policy( 0, index_space.size() ); 148 | auto value = _value; 149 | Kokkos::parallel_for( 150 | "CabanaPD::BC::apply", policy, KOKKOS_LAMBDA( const int b ) { 151 | auto pid = index_space( b ); 152 | for ( int d = 0; d < 3; d++ ) 153 | f( pid, d ) += value; 154 | } ); 155 | Kokkos::fence(); 156 | _timer.stop(); 157 | } 158 | 159 | auto forceUpdate() { return _force_update; } 160 | 161 | auto time() { return _timer.time(); }; 162 | auto timeInit() { return _indices.time(); }; 163 | }; 164 | 165 | template 166 | BoundaryCondition( BCTag, double, SteeringVectorType ) 167 | -> BoundaryCondition; 168 | 169 | template 170 | auto createBoundaryCondition( BCTag tag, const double value, 171 | ExecSpace exec_space, Particles particles, 172 | RegionType... regions ) 173 | { 174 | ParticleSteeringVector bc_indices( exec_space, particles, regions... ); 175 | return BoundaryCondition( tag, value, bc_indices ); 176 | } 177 | 178 | template 180 | auto createBoundaryCondition( UserFunctor user_functor, ExecSpace exec_space, 181 | Particles particles, const bool force_update, 182 | RegionType... regions ) 183 | { 184 | ParticleSteeringVector bc_indices( exec_space, particles, regions... ); 185 | return BoundaryCondition( bc_indices, user_functor, force_update ); 186 | } 187 | 188 | // Custom boundary particle cases. 189 | template 190 | auto createBoundaryCondition( BCTag, const double value, 191 | BoundaryParticles particles ) 192 | { 193 | ParticleSteeringVector bc_indices( particles ); 194 | return BoundaryCondition( value, bc_indices ); 195 | } 196 | 197 | template 198 | auto createBoundaryCondition( UserFunctor user_functor, 199 | BoundaryParticles particles, 200 | const bool force_update ) 201 | { 202 | ParticleSteeringVector bc_indices( particles ); 203 | return BoundaryCondition( bc_indices, user_functor, force_update ); 204 | } 205 | 206 | } // namespace CabanaPD 207 | 208 | #endif 209 | -------------------------------------------------------------------------------- /unit_test/tstComm.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | /**************************************************************************** 13 | * Copyright (c) 2018-2021 by the Cabana authors * 14 | * All rights reserved. * 15 | * * 16 | * This file is part of the Cabana library. Cabana is distributed under a * 17 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 18 | * the top-level directory. * 19 | * * 20 | * SPDX-License-Identifier: BSD-3-Clause * 21 | ****************************************************************************/ 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | namespace Test 36 | { 37 | //---------------------------------------------------------------------------// 38 | void testHalo() 39 | { 40 | using exec_space = TEST_EXECSPACE; 41 | using memory_space = TEST_MEMSPACE; 42 | 43 | std::array box_min = { -1.0, -1.0, -1.0 }; 44 | std::array box_max = { 1.0, 1.0, 1.0 }; 45 | std::array num_cells = { 10, 10, 10 }; 46 | 47 | double delta = 0.20000001; 48 | int halo_width = 2; 49 | // FIXME: This is for m = 1; should be calculated from m 50 | int expected_n = 6; 51 | CabanaPD::Particles particles( memory_space{}, CabanaPD::PMB{}, 52 | CabanaPD::TemperatureIndependent{} ); 53 | particles.domain( box_min, box_max, num_cells, halo_width ); 54 | particles.create( exec_space{} ); 55 | 56 | // Set ID equal to MPI rank. 57 | int current_rank = -1; 58 | MPI_Comm_rank( MPI_COMM_WORLD, ¤t_rank ); 59 | 60 | // No ints are communicated in CabanaPD. We use the volume field for MPI 61 | // rank here for convenience. 62 | auto rank = particles.sliceVolume(); 63 | auto x = particles.sliceReferencePosition(); 64 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 65 | { 66 | rank( pid ) = static_cast( current_rank ); 67 | }; 68 | particles.update( exec_space{}, init_functor ); 69 | 70 | int init_num_particles = particles.localOffset(); 71 | using HostAoSoA = Cabana::AoSoA, 72 | Kokkos::HostSpace>; 73 | HostAoSoA aosoa_init_host( "host_aosoa", init_num_particles ); 74 | auto x_init_host = Cabana::slice<0>( aosoa_init_host ); 75 | auto rank_init_host = Cabana::slice<1>( aosoa_init_host ); 76 | Cabana::deep_copy( x_init_host, x ); 77 | Cabana::deep_copy( rank_init_host, rank ); 78 | 79 | // A gather is performed on construction. 80 | using particles_type = 81 | CabanaPD::Particles; 83 | CabanaPD::Comm 85 | comm( particles ); 86 | 87 | HostAoSoA aosoa_host( "host_aosoa", particles.referenceOffset() ); 88 | x = particles.sliceReferencePosition(); 89 | rank = particles.sliceVolume(); 90 | auto x_host = Cabana::slice<0>( aosoa_host ); 91 | auto rank_host = Cabana::slice<1>( aosoa_host ); 92 | Cabana::deep_copy( x_host, x ); 93 | Cabana::deep_copy( rank_host, rank ); 94 | 95 | EXPECT_EQ( particles.localOffset(), init_num_particles ); 96 | 97 | // Check all local particles unchanged. 98 | for ( std::size_t p = 0; p < particles.localOffset(); ++p ) 99 | { 100 | for ( int d = 0; d < 3; ++d ) 101 | { 102 | EXPECT_EQ( x_host( p, d ), x_init_host( p, d ) ); 103 | } 104 | EXPECT_EQ( rank_host( p ), rank_init_host( p ) ); 105 | } 106 | 107 | int current_size = -1; 108 | MPI_Comm_size( MPI_COMM_WORLD, ¤t_size ); 109 | // Ghosts should have been created for all but single rank systems. 110 | if ( current_size > 1 ) 111 | { 112 | EXPECT_GT( particles.numGhost(), 0 ); 113 | } 114 | // Check all ghost particles in the halo region. 115 | for ( std::size_t p = particles.localOffset(); 116 | p < particles.referenceOffset(); ++p ) 117 | { 118 | for ( int d = 0; d < 3; ++d ) 119 | { 120 | EXPECT_GE( x_host( p, d ), particles.ghost_mesh_lo[d] ); 121 | EXPECT_LE( x_host( p, d ), particles.ghost_mesh_hi[d] ); 122 | } 123 | EXPECT_NE( rank_host( p ), current_rank ); 124 | } 125 | 126 | double mesh_min[3] = { particles.ghost_mesh_lo[0], 127 | particles.ghost_mesh_lo[1], 128 | particles.ghost_mesh_lo[2] }; 129 | double mesh_max[3] = { particles.ghost_mesh_hi[0], 130 | particles.ghost_mesh_hi[1], 131 | particles.ghost_mesh_hi[2] }; 132 | using NeighListType = 133 | Cabana::VerletList; 135 | NeighListType nlist( x, 0, particles.localOffset(), delta, 1.0, mesh_min, 136 | mesh_max ); 137 | 138 | // Copy neighbors per particle to host. 139 | Kokkos::View num_neigh( "num_neighbors", 140 | particles.localOffset() ); 141 | Kokkos::RangePolicy policy( 0, particles.localOffset() ); 142 | Kokkos::parallel_for( 143 | "num_neighbors", policy, KOKKOS_LAMBDA( const int p ) { 144 | auto n = 145 | Cabana::NeighborList::numNeighbor( nlist, p ); 146 | num_neigh( p ) = n; 147 | } ); 148 | 149 | // Check that all local particles (away from global boundaries) have a full 150 | // set of neighbors. 151 | // FIXME: Expected neighbors per particle could also be calculated at the 152 | // boundaries (less than internal particles). 153 | auto num_neigh_host = 154 | Kokkos::create_mirror_view_and_copy( Kokkos::HostSpace{}, num_neigh ); 155 | for ( std::size_t p = 0; p < particles.localOffset(); ++p ) 156 | { 157 | if ( x_host( p, 0 ) > box_min[0] + delta * 1.01 && 158 | x_host( p, 0 ) < box_max[0] - delta * 1.01 && 159 | x_host( p, 1 ) > box_min[1] + delta * 1.01 && 160 | x_host( p, 1 ) < box_max[1] - delta * 1.01 && 161 | x_host( p, 2 ) > box_min[2] + delta * 1.01 && 162 | x_host( p, 2 ) < box_max[2] - delta * 1.01 ) 163 | { 164 | EXPECT_EQ( num_neigh_host( p ), expected_n ); 165 | } 166 | } 167 | } 168 | 169 | //---------------------------------------------------------------------------// 170 | // TESTS 171 | //---------------------------------------------------------------------------// 172 | TEST( TEST_CATEGORY, test_particle_halo ) { testHalo(); } 173 | 174 | //---------------------------------------------------------------------------// 175 | 176 | } // end namespace Test 177 | -------------------------------------------------------------------------------- /examples/mechanics/crack_inclusion.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate a crack interacting with an inclusion. 22 | void crackInclusionExample( const std::string filename ) 23 | { 24 | // ==================================================== 25 | // Choose Kokkos spaces 26 | // ==================================================== 27 | using exec_space = Kokkos::DefaultExecutionSpace; 28 | using memory_space = typename exec_space::memory_space; 29 | 30 | // ==================================================== 31 | // Read inputs 32 | // ==================================================== 33 | CabanaPD::Inputs inputs( filename ); 34 | 35 | // ==================================================== 36 | // Material parameters 37 | // ==================================================== 38 | // Plate 39 | double rho0 = inputs["density"][0]; 40 | double E = inputs["elastic_modulus"][0]; 41 | double nu = inputs["Poisson's_ratio"][0]; 42 | double K = E / ( 3 * ( 1 - 2 * nu ) ); 43 | double G = E / ( 2 * ( 1 + nu ) ); 44 | double G0 = inputs["fracture_energy"][0]; 45 | 46 | // Inclusion 47 | double rho0_I = inputs["density"][1]; 48 | double E_I = inputs["elastic_modulus"][1]; 49 | double nu_I = inputs["Poisson's_ratio"][1]; 50 | double K_I = E_I / ( 3 * ( 1 - 2 * nu_I ) ); 51 | double G_I = E_I / ( 2 * ( 1 + nu_I ) ); 52 | double G0_I = inputs["fracture_energy"][1]; 53 | 54 | double horizon = inputs["horizon"]; 55 | horizon += 1e-10; 56 | 57 | // ==================================================== 58 | // Discretization 59 | // ==================================================== 60 | std::array low_corner = inputs["low_corner"]; 61 | std::array high_corner = inputs["high_corner"]; 62 | 63 | // ==================================================== 64 | // Pre-notch 65 | // ==================================================== 66 | double height = inputs["system_size"][0]; 67 | double thickness = inputs["system_size"][2]; 68 | double L_prenotch = height / 2.0; 69 | double y_prenotch = 0.5 * ( low_corner[1] + high_corner[1] ); 70 | Kokkos::Array p01 = { low_corner[0], y_prenotch, low_corner[2] }; 71 | Kokkos::Array v1 = { L_prenotch, 0, 0 }; 72 | Kokkos::Array v2 = { 0, 0, thickness }; 73 | Kokkos::Array, 1> notch_positions = { p01 }; 74 | CabanaPD::Prenotch<1> prenotch( v1, v2, notch_positions ); 75 | 76 | // ==================================================== 77 | // Force models 78 | // ==================================================== 79 | using model_type = CabanaPD::LPS; 80 | 81 | // Plate material 82 | CabanaPD::ForceModel force_model_plate( model_type{}, horizon, K, G, G0 ); 83 | 84 | // Inclusion material 85 | CabanaPD::ForceModel force_model_inclusion( model_type{}, horizon, K_I, G_I, 86 | G0_I ); 87 | 88 | // ==================================================== 89 | // Particle generation 90 | // ==================================================== 91 | // Note that individual inputs can be passed instead (see other examples). 92 | CabanaPD::Particles particles( memory_space{}, model_type{} ); 93 | particles.domain( inputs ); 94 | particles.create( exec_space{} ); 95 | 96 | // ==================================================== 97 | // Boundary conditions planes 98 | // ==================================================== 99 | double dy = particles.dx[1]; 100 | CabanaPD::Region plane1( 101 | low_corner[0], high_corner[0], low_corner[1] - dy, low_corner[1] + dy, 102 | low_corner[2], high_corner[2] ); 103 | CabanaPD::Region plane2( 104 | low_corner[0], high_corner[0], high_corner[1] - dy, high_corner[1] + dy, 105 | low_corner[2], high_corner[2] ); 106 | 107 | // ==================================================== 108 | // Custom particle initialization 109 | // ==================================================== 110 | auto rho = particles.sliceDensity(); 111 | auto x = particles.sliceReferencePosition(); 112 | auto v = particles.sliceVelocity(); 113 | auto f = particles.sliceForce(); 114 | auto nofail = particles.sliceNoFail(); 115 | auto type = particles.sliceType(); 116 | 117 | double R = inputs["inclusion_radius"]; 118 | double xI = inputs["inclusion_center"][0]; 119 | double yI = inputs["inclusion_center"][1]; 120 | 121 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 122 | { 123 | // Density 124 | rho( pid ) = rho0; 125 | // No-fail zone 126 | if ( x( pid, 1 ) <= plane1.low[1] + horizon + 1e-10 || 127 | x( pid, 1 ) >= plane2.high[1] - horizon - 1e-10 ) 128 | nofail( pid ) = 1; 129 | // Distance squared from inclusion center on the XY-plane 130 | double rsq = ( x( pid, 0 ) - xI ) * ( x( pid, 0 ) - xI ) + 131 | ( x( pid, 1 ) - yI ) * ( x( pid, 1 ) - yI ); 132 | // Inclusion material 133 | if ( rsq < R * R ) 134 | { 135 | type( pid ) = 1; 136 | rho( pid ) = rho0_I; 137 | } 138 | // Plate material 139 | else 140 | { 141 | rho( pid ) = rho0; 142 | } 143 | }; 144 | particles.update( exec_space{}, init_functor ); 145 | 146 | // ==================================================== 147 | // Create solver 148 | // ==================================================== 149 | auto models = CabanaPD::createMultiForceModel( 150 | particles, CabanaPD::AverageTag{}, force_model_plate, 151 | force_model_inclusion ); 152 | CabanaPD::Solver solver( inputs, particles, models ); 153 | 154 | // ==================================================== 155 | // Boundary conditions 156 | // ==================================================== 157 | // Create BC last to ensure ghost particles are included. 158 | double sigma0 = inputs["traction"]; 159 | double b0 = sigma0 / dy; 160 | f = solver.particles.sliceForce(); 161 | x = solver.particles.sliceReferencePosition(); 162 | // Create a symmetric force BC in the y-direction. 163 | auto bc_op = KOKKOS_LAMBDA( const int pid, const double ) 164 | { 165 | auto ypos = x( pid, 1 ); 166 | auto sign = std::abs( ypos ) / ypos; 167 | f( pid, 1 ) += b0 * sign; 168 | }; 169 | auto bc = createBoundaryCondition( bc_op, exec_space{}, solver.particles, 170 | true, plane1, plane2 ); 171 | 172 | // ==================================================== 173 | // Simulation run 174 | // ==================================================== 175 | solver.init( bc, prenotch ); 176 | solver.run( bc ); 177 | } 178 | 179 | // Initialize MPI+Kokkos. 180 | int main( int argc, char* argv[] ) 181 | { 182 | MPI_Init( &argc, &argv ); 183 | Kokkos::initialize( argc, argv ); 184 | 185 | crackInclusionExample( argv[1] ); 186 | 187 | Kokkos::finalize(); 188 | MPI_Finalize(); 189 | } 190 | -------------------------------------------------------------------------------- /examples/mechanics/fragmenting_cylinder.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright (c) 2022-2023 by Oak Ridge National Laboratory * 3 | * All rights reserved. * 4 | * * 5 | * This file is part of CabanaPD. CabanaPD is distributed under a * 6 | * BSD 3-clause license. For the licensing terms see the LICENSE file in * 7 | * the top-level directory. * 8 | * * 9 | * SPDX-License-Identifier: BSD-3-Clause * 10 | ****************************************************************************/ 11 | 12 | #include 13 | #include 14 | 15 | #include "mpi.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | // Simulate an expanding cylinder resulting in fragmentation. 22 | void fragmentingCylinderExample( const std::string filename ) 23 | { 24 | // ==================================================== 25 | // Choose Kokkos spaces 26 | // ==================================================== 27 | using exec_space = Kokkos::DefaultExecutionSpace; 28 | using memory_space = typename exec_space::memory_space; 29 | 30 | // ==================================================== 31 | // Read inputs 32 | // ==================================================== 33 | CabanaPD::Inputs inputs( filename ); 34 | 35 | // ==================================================== 36 | // Material parameters 37 | // ==================================================== 38 | double rho0 = inputs["density"]; 39 | double K = inputs["bulk_modulus"]; 40 | // double G = inputs["shear_modulus"]; // Only for LPS. 41 | double sc = inputs["critical_stretch"]; 42 | double horizon = inputs["horizon"]; 43 | horizon += 1e-10; 44 | // For PMB or LPS with influence_type == 1 45 | double G0 = 9 * K * horizon * ( sc * sc ) / 5; 46 | // For LPS with influence_type == 0 (default) 47 | // double G0 = 15 * K * horizon * ( sc * sc ) / 8; 48 | 49 | // ==================================================== 50 | // Discretization 51 | // ==================================================== 52 | std::array low_corner = inputs["low_corner"]; 53 | std::array high_corner = inputs["high_corner"]; 54 | std::array num_cells = inputs["num_cells"]; 55 | int m = std::floor( horizon / 56 | ( ( high_corner[0] - low_corner[0] ) / num_cells[0] ) ); 57 | int halo_width = m + 1; // Just to be safe. 58 | 59 | // ==================================================== 60 | // Force model 61 | // ==================================================== 62 | using model_type = CabanaPD::PMB; 63 | CabanaPD::ForceModel force_model( model_type{}, horizon, K, G0 ); 64 | 65 | // ==================================================== 66 | // Custom particle generation and initialization 67 | // ==================================================== 68 | double x_center = 0.5 * ( low_corner[0] + high_corner[0] ); 69 | double y_center = 0.5 * ( low_corner[1] + high_corner[1] ); 70 | double z_center = 0.5 * ( low_corner[2] + high_corner[2] ); 71 | double Rout = inputs["cylinder_outer_radius"]; 72 | double Rin = inputs["cylinder_inner_radius"]; 73 | double H = inputs["cylinder_height"]; 74 | 75 | // Do not create particles outside given cylindrical region 76 | auto init_op = KOKKOS_LAMBDA( const int, const double x[3] ) 77 | { 78 | double rsq = ( x[0] - x_center ) * ( x[0] - x_center ) + 79 | ( x[1] - y_center ) * ( x[1] - y_center ); 80 | if ( rsq < Rin * Rin || rsq > Rout * Rout || 81 | x[2] > z_center + 0.5 * H || x[2] < z_center - 0.5 * H ) 82 | return false; 83 | return true; 84 | }; 85 | 86 | // ==================================================== 87 | // Simulation run with contact physics 88 | // ==================================================== 89 | if ( inputs["use_contact"] ) 90 | { 91 | using contact_type = CabanaPD::NormalRepulsionModel; 92 | CabanaPD::Particles particles( memory_space{}, contact_type{} ); 93 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 94 | particles.create( exec_space{}, Cabana::InitRandom{}, init_op ); 95 | 96 | auto rho = particles.sliceDensity(); 97 | auto x = particles.sliceReferencePosition(); 98 | auto v = particles.sliceVelocity(); 99 | auto f = particles.sliceForce(); 100 | auto dx = particles.dx; 101 | 102 | double vrmax = inputs["max_radial_velocity"]; 103 | double vrmin = inputs["min_radial_velocity"]; 104 | double vzmax = inputs["max_vertical_velocity"]; 105 | double zmin = z_center - 0.5 * H; 106 | 107 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 108 | { 109 | // Density 110 | rho( pid ) = rho0; 111 | 112 | // Velocity 113 | double zfactor = ( ( x( pid, 2 ) - zmin ) / ( 0.5 * H ) ) - 1; 114 | double vr = vrmax - vrmin * zfactor * zfactor; 115 | v( pid, 0 ) = 116 | vr * Kokkos::cos( Kokkos::atan2( x( pid, 1 ), x( pid, 0 ) ) ); 117 | v( pid, 1 ) = 118 | vr * Kokkos::sin( Kokkos::atan2( x( pid, 1 ), x( pid, 0 ) ) ); 119 | v( pid, 2 ) = vzmax * zfactor; 120 | }; 121 | particles.update( exec_space{}, init_functor ); 122 | 123 | // Use contact radius and extension relative to particle spacing. 124 | double r_c = inputs["contact_horizon_factor"]; 125 | double r_extend = inputs["contact_horizon_extend_factor"]; 126 | // NOTE: dx/2 is when particles first touch. 127 | r_c *= dx[0] / 2.0; 128 | r_extend *= dx[0]; 129 | 130 | contact_type contact_model( horizon, r_c, r_extend, K ); 131 | 132 | CabanaPD::Solver solver( inputs, particles, force_model, 133 | contact_model ); 134 | solver.init(); 135 | solver.run(); 136 | } 137 | // ==================================================== 138 | // Simulation run without contact 139 | // ==================================================== 140 | else 141 | { 142 | CabanaPD::Particles particles( memory_space{}, model_type{} ); 143 | particles.domain( low_corner, high_corner, num_cells, halo_width ); 144 | particles.create( exec_space{}, Cabana::InitRandom{}, init_op ); 145 | 146 | auto rho = particles.sliceDensity(); 147 | auto x = particles.sliceReferencePosition(); 148 | auto v = particles.sliceVelocity(); 149 | auto f = particles.sliceForce(); 150 | 151 | double vrmax = inputs["max_radial_velocity"]; 152 | double vrmin = inputs["min_radial_velocity"]; 153 | double vzmax = inputs["max_vertical_velocity"]; 154 | double zmin = z_center - 0.5 * H; 155 | 156 | auto init_functor = KOKKOS_LAMBDA( const int pid ) 157 | { 158 | // Density 159 | rho( pid ) = rho0; 160 | 161 | // Velocity 162 | double zfactor = ( ( x( pid, 2 ) - zmin ) / ( 0.5 * H ) ) - 1; 163 | double vr = vrmax - vrmin * zfactor * zfactor; 164 | v( pid, 0 ) = 165 | vr * Kokkos::cos( Kokkos::atan2( x( pid, 1 ), x( pid, 0 ) ) ); 166 | v( pid, 1 ) = 167 | vr * Kokkos::sin( Kokkos::atan2( x( pid, 1 ), x( pid, 0 ) ) ); 168 | v( pid, 2 ) = vzmax * zfactor; 169 | }; 170 | particles.update( exec_space{}, init_functor ); 171 | 172 | CabanaPD::Solver solver( inputs, particles, force_model ); 173 | solver.init(); 174 | solver.run(); 175 | } 176 | } 177 | 178 | // Initialize MPI+Kokkos. 179 | int main( int argc, char* argv[] ) 180 | { 181 | MPI_Init( &argc, &argv ); 182 | Kokkos::initialize( argc, argv ); 183 | 184 | fragmentingCylinderExample( argv[1] ); 185 | 186 | Kokkos::finalize(); 187 | MPI_Finalize(); 188 | } 189 | --------------------------------------------------------------------------------