diff --git a/.circleci/config.yml b/.circleci/config.yml index 484f916f2..3a9b1a369 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,8 @@ jobs: export MODULEPATH=$MODULEPATH:/usr/share/modulefiles module load mpi/openmpi-x86_64 export CXX_COMPILER=mpicxx + export PETSC_ARCH=arch-linux2-c-opt + export PETSC_DIR=/home/cbgeo/petsc/ cmake -GNinja -DCMAKE_CXX_COMPILER=mpicxx -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DKAHIP_ROOT=/home/cbgeo/KaHIP/ -DPARTIO_ROOT=/home/cbgeo/partio/ .. ninja -j2 ctest -VV @@ -32,6 +34,8 @@ jobs: mkdir -p build [ "$(ls -A build)" ] && rm -rf build/* cd build + export PETSC_ARCH=arch-linux2-c-opt + export PETSC_DIR=/home/cbgeo/petsc/ scan-build cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DKAHIP_ROOT=/home/cbgeo/KaHIP/ -DPARTIO_ROOT=/home/cbgeo/partio/ .. scan-build -k -V ninja -j2 ctest -VV @@ -64,6 +68,8 @@ jobs: mkdir -p build [ "$(ls -A build)" ] && rm -rf build/* cd build + export PETSC_ARCH=arch-linux2-c-opt + export PETSC_DIR=/home/cbgeo/petsc/ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=mpicxx -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DENABLE_COVERAGE=On -DKAHIP_ROOT=/home/cbgeo/KaHIP/ -DPARTIO_ROOT=/home/cbgeo/partio/ .. make mpmtest_coverage -j2 ./mpmtest_coverage @@ -140,4 +146,4 @@ workflows: filters: branches: only: - - develop \ No newline at end of file + - master \ No newline at end of file diff --git a/.gitignore b/.gitignore index 602feda34..a94dc7583 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,4 @@ configure.scan *missing *stamp-h1 benchmarks/ +bin/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 7700b30ba..06cd7600b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,9 @@ option(MPM_BUILD_TESTING "enable testing for mpm" ON) # Halo exchange option(HALO_EXCHANGE "Enable halo exchange" OFF) +# PETSC +option(USE_PETSC "Use PETSC solver library" OFF) + # CMake Modules set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) @@ -71,6 +74,7 @@ if (HDF5_FOUND) include_directories(${HDF5_INCLUDE_DIRS}) link_libraries(${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES} ${HDF5_CXX_HL_LIBRARIES}) add_definitions(${HDF5_DEFINITIONS}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof") endif() # OpenMP @@ -97,6 +101,15 @@ if (MKL_FOUND) endif() endif() +# PETSc +if (USE_PETSC) + find_package(PETSc COMPONENTS C) +endif() +if (PETSC_FOUND) + include_directories(${PETSC_INC}) + link_libraries(${PETSC_LIB}) + add_definitions("-DUSE_PETSC") +endif() # KaHIP if (MPI_FOUND) @@ -131,6 +144,7 @@ endif() # Include directories include_directories(BEFORE ${mpm_SOURCE_DIR}/include/ + ${mpm_SOURCE_DIR}/include/contacts/ ${mpm_SOURCE_DIR}/include/containers/ ${mpm_SOURCE_DIR}/include/elements/ ${mpm_SOURCE_DIR}/include/elements/2d @@ -138,12 +152,14 @@ include_directories(BEFORE ${mpm_SOURCE_DIR}/include/functions/ ${mpm_SOURCE_DIR}/include/generators/ ${mpm_SOURCE_DIR}/include/io/ - ${mpm_SOURCE_DIR}/include/contacts/ + ${mpm_SOURCE_DIR}/include/linear_solvers/ ${mpm_SOURCE_DIR}/include/loads_bcs/ ${mpm_SOURCE_DIR}/include/materials/ ${mpm_SOURCE_DIR}/include/particles/ + ${mpm_SOURCE_DIR}/include/particles/pod_particles ${mpm_SOURCE_DIR}/include/solvers/ ${mpm_SOURCE_DIR}/include/solvers/mpm_scheme/ + ${mpm_SOURCE_DIR}/include/utilities/ ${mpm_SOURCE_DIR}/external/ ${mpm_SOURCE_DIR}/tests/include/ ) @@ -157,12 +173,14 @@ SET(mpm_src ${mpm_SOURCE_DIR}/src/functions/linear_function.cc ${mpm_SOURCE_DIR}/src/functions/sin_function.cc ${mpm_SOURCE_DIR}/src/geometry.cc - ${mpm_SOURCE_DIR}/src/hdf5_particle.cc + ${mpm_SOURCE_DIR}/src/pod_particle.cc + ${mpm_SOURCE_DIR}/src/pod_particle_twophase.cc ${mpm_SOURCE_DIR}/src/io/io.cc ${mpm_SOURCE_DIR}/src/io/io_mesh.cc ${mpm_SOURCE_DIR}/src/io/logger.cc ${mpm_SOURCE_DIR}/src/io/partio_writer.cc ${mpm_SOURCE_DIR}/src/io/vtk_writer.cc + ${mpm_SOURCE_DIR}/src/linear_solver.cc ${mpm_SOURCE_DIR}/src/material.cc ${mpm_SOURCE_DIR}/src/mpm.cc ${mpm_SOURCE_DIR}/src/nodal_properties.cc @@ -200,6 +218,7 @@ if(MPM_BUILD_TESTING) ${mpm_SOURCE_DIR}/tests/elements/triangle_quadrature_test.cc ${mpm_SOURCE_DIR}/tests/functions/linear_function_test.cc ${mpm_SOURCE_DIR}/tests/functions/sin_function_test.cc + ${mpm_SOURCE_DIR}/tests/functions/radial_basis_function_test.cc ${mpm_SOURCE_DIR}/tests/graph_test.cc ${mpm_SOURCE_DIR}/tests/interface_test.cc ${mpm_SOURCE_DIR}/tests/io/io_mesh_ascii_test.cc @@ -207,6 +226,7 @@ if(MPM_BUILD_TESTING) ${mpm_SOURCE_DIR}/tests/io/vtk_writer_test.cc ${mpm_SOURCE_DIR}/tests/io/write_mesh_particles.cc ${mpm_SOURCE_DIR}/tests/io/write_mesh_particles_unitcell.cc + ${mpm_SOURCE_DIR}/tests/linear_solver_test.cc ${mpm_SOURCE_DIR}/tests/materials/bingham_test.cc ${mpm_SOURCE_DIR}/tests/materials/linear_elastic_test.cc ${mpm_SOURCE_DIR}/tests/materials/modified_cam_clay_test.cc @@ -214,27 +234,39 @@ if(MPM_BUILD_TESTING) ${mpm_SOURCE_DIR}/tests/materials/newtonian_test.cc ${mpm_SOURCE_DIR}/tests/materials/norsand_test.cc ${mpm_SOURCE_DIR}/tests/materials/material_utility_test.cc + ${mpm_SOURCE_DIR}/tests/mesh_free_surface_test.cc ${mpm_SOURCE_DIR}/tests/mesh_neighbours_test.cc ${mpm_SOURCE_DIR}/tests/mesh_test_2d.cc ${mpm_SOURCE_DIR}/tests/mesh_test_3d.cc ${mpm_SOURCE_DIR}/tests/mpi_transfer_particle_test.cc + ${mpm_SOURCE_DIR}/tests/nodes/nodal_properties_test.cc + ${mpm_SOURCE_DIR}/tests/nodes/node_map_test.cc + ${mpm_SOURCE_DIR}/tests/nodes/node_test.cc + ${mpm_SOURCE_DIR}/tests/nodes/node_twophase_test.cc + ${mpm_SOURCE_DIR}/tests/nodes/node_vector_test.cc + ${mpm_SOURCE_DIR}/tests/particles/particle_cell_crossing_test.cc + ${mpm_SOURCE_DIR}/tests/particles/particle_test.cc + ${mpm_SOURCE_DIR}/tests/particles/particle_twophase_test.cc + ${mpm_SOURCE_DIR}/tests/particles/particle_traction_test.cc + ${mpm_SOURCE_DIR}/tests/particles/particle_vector_test.cc + ${mpm_SOURCE_DIR}/tests/particles/particle_serialize_deserialize_test.cc + ${mpm_SOURCE_DIR}/tests/particles/particle_serialize_deserialize_twophase_test.cc + ${mpm_SOURCE_DIR}/tests/point_in_cell_test.cc ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_musl_test.cc ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_musl_unitcell_test.cc ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_usf_test.cc ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_usf_unitcell_test.cc ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_usl_test.cc ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_usl_unitcell_test.cc + ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_twophase_usf_test.cc + ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_twophase_usf_unitcell_test.cc + ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_twophase_usl_test.cc + ${mpm_SOURCE_DIR}/tests/solvers/mpm_explicit_twophase_usl_unitcell_test.cc + ${mpm_SOURCE_DIR}/tests/solvers/mpm_semi_implicit_navierstokes_test.cc + ${mpm_SOURCE_DIR}/tests/solvers/mpm_semi_implicit_navierstokes_unitcell_test.cc + ${mpm_SOURCE_DIR}/tests/solvers/mpm_semi_implicit_twophase_test.cc + ${mpm_SOURCE_DIR}/tests/solvers/mpm_semi_implicit_twophase_unitcell_test.cc ${mpm_SOURCE_DIR}/tests/solvers/mpm_scheme_test.cc - ${mpm_SOURCE_DIR}/tests/nodal_properties_test.cc - ${mpm_SOURCE_DIR}/tests/node_map_test.cc - ${mpm_SOURCE_DIR}/tests/node_test.cc - ${mpm_SOURCE_DIR}/tests/node_vector_test.cc - ${mpm_SOURCE_DIR}/tests/particle_cell_crossing_test.cc - ${mpm_SOURCE_DIR}/tests/particle_serialize_deserialize_test.cc - ${mpm_SOURCE_DIR}/tests/particle_test.cc - ${mpm_SOURCE_DIR}/tests/particle_traction_test.cc - ${mpm_SOURCE_DIR}/tests/particle_vector_test.cc - ${mpm_SOURCE_DIR}/tests/point_in_cell_test.cc ) add_executable(mpmtest ${mpm_src} ${test_src}) add_test(NAME mpmtest COMMAND $) diff --git a/cmake/FindPETSc.cmake b/cmake/FindPETSc.cmake new file mode 100644 index 000000000..dedbe2d84 --- /dev/null +++ b/cmake/FindPETSc.cmake @@ -0,0 +1,154 @@ +################################################################# +# Try to find PETSc # +# # +# Once done this will define: # +# PETSC_FOUND - system has PETSc # +# PETSC_DIR - PETSc directory # +# PETSC_ARCH - PETSc architecture # +# PETSC_INC - PETSc include directory # +# PETSC_LIB - PETSc library (static or dynamic) # +# PETSC_VARIABLES - Content of PETSc 'petscvariables' file # +# # +# PETSC_MUMPS - Was PETSc compiled with MUMPS? # +# PETSC_MUMPS_INC - PETSc MUMPS include file # +# PETSC_MUMPS_LIB - PETSc MUMPS libraries # +# # +# PETSC_SCALAPACK - Was PETSc compiled with ScaLAPACK? # +# PETSC_SCALAPACK_LIB - PETSc ScaLAPACK libraries # +# # +# PETSC_PARMETIS - Was PETSc compiled with ParMETIS? # +# PETSC_PARMETIS_LIB - PETSc ParMETIS libraries # +# # +# PETSC_METIS - Was PETSc compiled with Metis? # +# PETSC_METIS_LIB - PETSc METIS libraries # +# # +# PETSC_MPIUNI - Was PETSc compiled with MPIUNI? # +# PETSC_MPIUNI_INC - MPIUNI include file # +# # +# Usage: # +# find_package(PETSc) # +# # +# Setting these changes the behavior of the search # +# PETSC_DIR - PETSc directory # +# PETSC_ARCH - PETSc architecture # +################################################################# + +## Try to set PETSC_DIR and PETSC_ARCH ## +######################################### +if(NOT DEFINED PETSC_DIR) + set(PETSC_DIR $ENV{PETSC_DIR}) +endif() +if(NOT DEFINED PETSC_ARCH) + set(PETSC_ARCH $ENV{PETSC_ARCH}) +endif() + +## Includes ## +############## +if(EXISTS "${PETSC_DIR}/include" AND + EXISTS "${PETSC_DIR}/${PETSC_ARCH}/include") + set(PETSC_INC "${PETSC_DIR}/include" "${PETSC_DIR}/${PETSC_ARCH}/include") +else() + message(SEND_ERROR "PETSc includes not found") +endif() + +## Library ## +############# +if(EXISTS "${PETSC_DIR}/${PETSC_ARCH}/lib/libpetsc.so") + set(PETSC_LIB "${PETSC_DIR}/${PETSC_ARCH}/lib/libpetsc.so") +elseif(EXISTS "${PETSC_DIR}/${PETSC_ARCH}/lib/libpetsc.a") + set(PETSC_LIB "${PETSC_DIR}/${PETSC_ARCH}/lib/libpetsc.a") +else() + message(SEND_ERROR "PETSc library not found") +endif() + +## PETSc variables ## +##################### +if(EXISTS ${PETSC_DIR}/${PETSC_ARCH}/conf/petscvariables) + file(STRINGS ${PETSC_DIR}/${PETSC_ARCH}/conf/petscvariables + PETSC_VARIABLES NEWLINE_CONSUME) +elseif(EXISTS ${PETSC_DIR}/${PETSC_ARCH}/lib/petsc/conf/petscvariables) + file(STRINGS ${PETSC_DIR}/${PETSC_ARCH}/lib/petsc/conf/petscvariables + PETSC_VARIABLES NEWLINE_CONSUME) +else() + message(SEND_ERROR "PETSc variables not found") +endif() + +## MUMPS ## +########### +if(EXISTS ${PETSC_DIR}/${PETSC_ARCH}/lib/libmumps_common.a) + set(PETSC_MUMPS TRUE) + set(PETSC_MUMPS_INC ${PETSC_INC}) + set(PETSC_MUMPS_LIB + ${PETSC_DIR}/${PETSC_ARCH}/lib/libcmumps.a + ${PETSC_DIR}/${PETSC_ARCH}/lib/libdmumps.a + ${PETSC_DIR}/${PETSC_ARCH}/lib/libsmumps.a + ${PETSC_DIR}/${PETSC_ARCH}/lib/libzmumps.a + ${PETSC_DIR}/${PETSC_ARCH}/lib/libmumps_common.a + ${PETSC_DIR}/${PETSC_ARCH}/lib/libpord.a) + + if(EXISTS ${PETSC_DIR}/${PETSC_ARCH}/lib/libmpiseq.a) + set(PETSC_MUMPS_SEQ ${PETSC_DIR}/${PETSC_ARCH}/lib/libmpiseq.a) + else() + set(PETSC_MUMPS_SEQ "") + endif() + +else() + set(PETSC_MUMPS FALSE) + set(PETSC_MUMPS_INC "") + set(PETSC_MUMPS_LIB "") + set(PETSC_MUMPS_SEQ "") +endif() + +## ScaLAPACK ## +############### +if(EXISTS ${PETSC_DIR}/${PETSC_ARCH}/lib/libscalapack.a) + set(PETSC_SCALAPACK TRUE) + set(PETSC_SCALAPACK_LIB ${PETSC_DIR}/${PETSC_ARCH}/lib/libscalapack.a) +else() + set(PETSC_SCALAPACK FALSE) + set(PETSC_SCALAPACK_LIB "") +endif() + +## ParMETIS ## +############## +if(EXISTS ${PETSC_DIR}/${PETSC_ARCH}/lib/libparmetis.so) + set(PETSC_PARMETIS TRUE) + set(PETSC_PARMETIS_LIB ${PETSC_DIR}/${PETSC_ARCH}/lib/libparmetis.so) +else() + set(PETSC_PARMETIS FALSE) + set(PETSC_PARMETIS_LIB "") +endif() + +## METIS ## +############ +if(EXISTS ${PETSC_DIR}/${PETSC_ARCH}/lib/libmetis.so) + set(PETSC_METIS TRUE) + set(PETSC_METIS_LIB ${PETSC_DIR}/${PETSC_ARCH}/lib/libmetis.so) +else() + set(PETSC_METIS FALSE) + set(PETSC_METIS_LIB "") +endif() + +## MPI Uni ## +############# +string(REGEX MATCH "MPI_IS_MPIUNI = [^\n\r]*" PETSC_MPIUNI ${PETSC_VARIABLES}) +if(PETSC_MPIUNI) + string(REPLACE "MPI_IS_MPIUNI = " "" PETSC_MPIUNI ${PETSC_MPIUNI}) + string(STRIP ${PETSC_MPIUNI} PETSC_MPIUNI) + string(COMPARE EQUAL ${PETSC_MPIUNI} "1" PETSC_MPIUNI) + + string(REGEX MATCH "MPI_INCLUDE = -I[^\n\r]*" + PETSC_MPIUNI_INC ${PETSC_VARIABLES}) + string(REPLACE "MPI_INCLUDE = -I" "" PETSC_MPIUNI_INC ${PETSC_MPIUNI_INC}) + string(STRIP ${PETSC_MPIUNI_INC} PETSC_MPIUNI_INC) +else() + set(PETSC_MPIUNI FALSE) + set(PETSC_MPIUNI_INC "") +endif() + +## CMake check and done ## +########################## +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PETSc + "PETSc could not be found: be sure to set PETSC_DIR and PETSC_ARCH in your environment variables" + PETSC_LIB PETSC_INC PETSC_DIR PETSC_ARCH) diff --git a/cmake/git_watcher.cmake b/cmake/git_watcher.cmake index ab135e808..65e2c8bcc 100644 --- a/cmake/git_watcher.cmake +++ b/cmake/git_watcher.cmake @@ -311,4 +311,4 @@ function(Main) endfunction() # And off we go... -Main() +Main() \ No newline at end of file diff --git a/include/cell.h b/include/cell.h index 2380b7d3d..9764e56d8 100644 --- a/include/cell.h +++ b/include/cell.h @@ -217,6 +217,156 @@ class Cell { //! Return previous mpi rank unsigned previous_mpirank() const; + /** + * \defgroup MultiPhase Functions dealing with multi-phase MPM + */ + /**@{*/ + + //! Assign solving status + //! \ingroup MultiPhase + //! \param[in] status Cell solving status for parallel free-surface detection + void assign_solving_status(bool status) { solving_status_ = status; } + + //! Return solving status of a cell: active (if a particle is present in all + //! MPI rank) + //! \ingroup MultiPhase + bool solving_status() const { return solving_status_; } + + //! Assign free surface + //! \ingroup MultiPhase + //! \param[in] free_surface boolean indicating free surface cell + void assign_free_surface(bool free_surface) { free_surface_ = free_surface; }; + + //! Return free surface bool + //! \ingroup MultiPhase + //! \retval free_surface_ indicating free surface cell + bool free_surface() const { return free_surface_; }; + + //! Assign volume traction to node + //! \ingroup MultiPhase + //! \param[in] volume_fraction cell volume fraction + void assign_volume_fraction(double volume_fraction) { + volume_fraction_ = volume_fraction; + }; + + //! Return cell volume fraction + //! \ingroup MultiPhase + //! \retval volume_fraction_ cell volume fraction + double volume_fraction() const { return volume_fraction_; }; + + //! Map cell volume to the nodes + //! \ingroup MultiPhase + //! \param[in] phase to map volume + void map_cell_volume_to_nodes(unsigned phase); + + //! Initialize local elemental matrices + //! \ingroup MultiPhase + bool initialise_element_matrix(); + + //! Initialize local elemental matrices for two-phase one-point solver + //! \ingroup MultiPhase + bool initialise_element_matrix_twophase(); + + //! Return local node indices + //! \ingroup MultiPhase + Eigen::VectorXi local_node_indices(); + + //! Return drag matrix for two-phase one-point solver + //! \ingroup MultiPhase + //! \param[in] dir Desired direction + const Eigen::MatrixXd& drag_matrix(unsigned dir) { + return drag_matrix_[dir]; + }; + + //! Compute local matrix for drag force coupling for two-phase one-point + //! solver + //! \ingroup MultiPhase + //! \param[in] shapefn Shape function + //! \param[in] pvolume Volume weight + //! \param[in] multiplier Drag force multiplier + void compute_local_drag_matrix(const Eigen::VectorXd& shapefn, double pvolume, + const VectorDim& multiplier) noexcept; + + //! Return local laplacian + //! \ingroup MultiPhase + const Eigen::MatrixXd& laplacian_matrix() { return laplacian_matrix_; }; + + //! Compute local laplacian matrix (Used in poisson equation) + //! \ingroup MultiPhase + //! \param[in] grad_shapefn shape function gradient + //! \param[in] pvolume volume weight + //! \param[in] multiplier multiplier + void compute_local_laplacian(const Eigen::MatrixXd& grad_shapefn, + double pvolume, + double multiplier = 1.0) noexcept; + + //! Return local laplacian RHS matrix + //! \ingroup MultiPhase + const Eigen::MatrixXd& poisson_right_matrix() { + return poisson_right_matrix_; + }; + + //! Return local laplacian RHS matrix for twophase + //! \ingroup MultiPhase + //! \param[in] phase Phase identifier + const Eigen::MatrixXd& poisson_right_matrix(unsigned phase) { + return poisson_right_matrix_twophase_[phase]; + }; + + //! Compute local poisson RHS matrix (Used in poisson equation) + //! \ingroup MultiPhase + //! \param[in] shapefn shape function + //! \param[in] grad_shapefn shape function gradient + //! \param[in] pvolume volume weight + void compute_local_poisson_right(const Eigen::VectorXd& shapefn, + const Eigen::MatrixXd& grad_shapefn, + double pvolume, + double multiplier = 1.0) noexcept; + + //! Compute local poisson RHS matrix (Used in poisson equation) + //! \ingroup MultiPhase + //! \param[in] phase Phase identifier + //! \param[in] shapefn shape function + //! \param[in] grad_shapefn shape function gradient + //! \param[in] pvolume volume weight + void compute_local_poisson_right_twophase(unsigned phase, + const Eigen::VectorXd& shapefn, + const Eigen::MatrixXd& grad_shapefn, + double pvolume, + double multiplier = 1.0) noexcept; + + //! Return local correction matrix + //! \ingroup MultiPhase + const Eigen::MatrixXd& correction_matrix() { return correction_matrix_; }; + + //! Return local correction matrix for two phase + //! \ingroup MultiPhase + //! \param[in] phase Phase identifier + const Eigen::MatrixXd& correction_matrix(unsigned phase) { + return correction_matrix_twophase_[phase]; + }; + + //! Compute local correction matrix (Used to correct velocity) + //! \ingroup MultiPhase + //! \param[in] shapefn shape function + //! \param[in] grad_shapefn shape function gradient + //! \param[in] pvolume volume weight + void compute_local_correction_matrix(const Eigen::VectorXd& shapefn, + const Eigen::MatrixXd& grad_shapefn, + double pvolume) noexcept; + + //! Compute local correction matrix for two phase (Used to correct velocity) + //! \param[in] phase Phase identifier + //! \param[in] shapefn shape function + //! \param[in] grad_shapefn shape function gradient + //! \param[in] pvolume volume weight + void compute_local_correction_matrix_twophase( + unsigned phase, const Eigen::VectorXd& shapefn, + const Eigen::MatrixXd& grad_shapefn, double pvolume, + double multiplier = 1.0) noexcept; + + /**@}*/ + private: //! Approximately check if a point is in a cell //! \param[in] point Coordinates of point @@ -264,11 +414,37 @@ class Cell { //! Normal of face //! first-> face_id, second->vector of the normal std::map face_normals_; + + /** + * \defgroup MultiPhaseVariables Variables for multi-phase MPM + * @{ + */ + //! Solving status + bool solving_status_{false}; + //! Free surface bool + bool free_surface_{false}; + //! Volume fraction + double volume_fraction_{0.0}; + //! Local laplacian matrix + Eigen::MatrixXd laplacian_matrix_; + //! Local poisson RHS matrix + Eigen::MatrixXd poisson_right_matrix_; + //! Local correction RHS matrix + Eigen::MatrixXd correction_matrix_; + //! Drag force coefficient + std::vector drag_matrix_; + //! Local poisson RHS matrix for twophase + std::vector poisson_right_matrix_twophase_; + //! Local poisson RHS matrix for twophase + std::vector correction_matrix_twophase_; + /**@}*/ + //! Logger std::unique_ptr console_; }; // Cell class } // namespace mpm #include "cell.tcc" +#include "cell_multiphase.tcc" -#endif // MPM_CELL_H_ +#endif // MPM_CELL_H_ \ No newline at end of file diff --git a/include/cell.tcc b/include/cell.tcc index 05f839dab..48f65bcfa 100644 --- a/include/cell.tcc +++ b/include/cell.tcc @@ -412,7 +412,7 @@ inline Eigen::Matrix mpm::Cell<2>::local_coordinates_point( if (indices.size() == 3) { // 2 0 // |\ - // | \ + // | \ // c | \ b // | \ // | \ diff --git a/include/cell_multiphase.tcc b/include/cell_multiphase.tcc new file mode 100644 index 000000000..98ce305bf --- /dev/null +++ b/include/cell_multiphase.tcc @@ -0,0 +1,192 @@ +//! Map cell volume to nodes +template +void mpm::Cell::map_cell_volume_to_nodes(unsigned phase) { + if (this->status()) { + // Check if cell volume is set + if (volume_ <= std::numeric_limits::lowest()) + this->compute_volume(); + + for (unsigned i = 0; i < nodes_.size(); ++i) { + nodes_[i]->update_volume(true, phase, volume_ / nnodes_); + } + } +} + +//! Return local node indices +template +Eigen::VectorXi mpm::Cell::local_node_indices() { + Eigen::VectorXi indices; + try { + indices.resize(nodes_.size()); + indices.setZero(); + unsigned node_idx = 0; + for (auto node_itr = nodes_.cbegin(); node_itr != nodes_.cend(); + ++node_itr) { + indices(node_idx) = (*node_itr)->active_id(); + node_idx++; + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return indices; +} + +//! Initialise element matrix +template +bool mpm::Cell::initialise_element_matrix() { + bool status = true; + if (this->status()) { + try { + // Initialse Laplacian matrix (NxN) + laplacian_matrix_.resize(nnodes_, nnodes_); + laplacian_matrix_.setZero(); + + // Initialse poisson RHS matrix (Nx(N*Tdim)) + poisson_right_matrix_.resize(nnodes_, nnodes_ * Tdim); + poisson_right_matrix_.setZero(); + + // Initialse correction RHS matrix (NxTdim) + correction_matrix_.resize(nnodes_, nnodes_ * Tdim); + correction_matrix_.setZero(); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + } + return status; +} + +//! Initialize local elemental matrices for two-phase one-point solver +template +bool mpm::Cell::initialise_element_matrix_twophase() { + bool status = true; + if (this->status()) { + try { + // Initialise drag matrix + drag_matrix_.resize(Tdim); + for (unsigned dir = 0; dir < Tdim; dir++) { + drag_matrix_[dir].resize(nnodes_, nnodes_); + drag_matrix_[dir].setZero(); + } + + // Initialse Laplacian matrix (NxN) + laplacian_matrix_.resize(nnodes_, nnodes_); + laplacian_matrix_.setZero(); + + // Initialse poisson RHS matrix (Nx(N*Tdim)) + poisson_right_matrix_twophase_.resize(2); + for (unsigned phase = 0; phase < poisson_right_matrix_twophase_.size(); + phase++) { + poisson_right_matrix_twophase_[phase].resize(nnodes_, nnodes_ * Tdim); + poisson_right_matrix_twophase_[phase].setZero(); + } + + // Initialse correction RHS matrix (NxTdim) + correction_matrix_twophase_.resize(2); + for (unsigned phase = 0; phase < correction_matrix_twophase_.size(); + phase++) { + correction_matrix_twophase_[phase].resize(nnodes_, nnodes_ * Tdim); + correction_matrix_twophase_[phase].setZero(); + } + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + } + return status; +} + +//! Compute local matrix for drag force coupling +template +void mpm::Cell::compute_local_drag_matrix( + const Eigen::VectorXd& shapefn, double pvolume, + const VectorDim& multiplier) noexcept { + + assert(drag_matrix_.size() == Tdim); + + // Lock the storage + std::lock_guard guard(cell_mutex_); + // Compute local drag matrix + for (unsigned dir = 0; dir < Tdim; dir++) + drag_matrix_[dir] += + multiplier(dir) * pvolume * shapefn * (shapefn.transpose()); +} + +//! Compute local matrix of laplacian +template +void mpm::Cell::compute_local_laplacian( + const Eigen::MatrixXd& grad_shapefn, double pvolume, + double multiplier) noexcept { + + std::lock_guard guard(cell_mutex_); + laplacian_matrix_ += + grad_shapefn * grad_shapefn.transpose() * multiplier * pvolume; +} + +//! Compute local poisson RHS matrix +//! Used in poisson equation RHS for Navier Stokes solver +template +void mpm::Cell::compute_local_poisson_right( + const Eigen::VectorXd& shapefn, const Eigen::MatrixXd& grad_shapefn, + double pvolume, double multiplier) noexcept { + + std::lock_guard guard(cell_mutex_); + for (unsigned i = 0; i < Tdim; i++) { + poisson_right_matrix_.block(0, i * nnodes_, nnodes_, nnodes_) += + shapefn * grad_shapefn.col(i).transpose() * multiplier * pvolume; + } +} + +//! Compute local poisson RHS matrix +//! Used in poisson equation RHS for TwoPhase solver +template +void mpm::Cell::compute_local_poisson_right_twophase( + unsigned phase, const Eigen::VectorXd& shapefn, + const Eigen::MatrixXd& grad_shapefn, double pvolume, + double multiplier) noexcept { + + assert(phase < poisson_right_matrix_twophase_.size() && + poisson_right_matrix_twophase_.size() == 2); + + std::lock_guard guard(cell_mutex_); + for (unsigned i = 0; i < Tdim; i++) { + poisson_right_matrix_twophase_[phase].block(0, i * nnodes_, nnodes_, + nnodes_) += + shapefn * grad_shapefn.col(i).transpose() * multiplier * pvolume; + } +} + +//! Compute local correction matrix +//! Used to compute corrector of nodal velocity for Navier Stokes solver +template +void mpm::Cell::compute_local_correction_matrix( + const Eigen::VectorXd& shapefn, const Eigen::MatrixXd& grad_shapefn, + double pvolume) noexcept { + + std::lock_guard guard(cell_mutex_); + for (unsigned i = 0; i < Tdim; i++) { + correction_matrix_.block(0, i * nnodes_, nnodes_, nnodes_) += + shapefn * grad_shapefn.col(i).transpose() * pvolume; + } +} + +//! Compute local correction matrix +//! Used to compute corrector of nodal velocity for Two Phase solver +template +void mpm::Cell::compute_local_correction_matrix_twophase( + unsigned phase, const Eigen::VectorXd& shapefn, + const Eigen::MatrixXd& grad_shapefn, double pvolume, + double multiplier) noexcept { + + assert(phase < correction_matrix_twophase_.size() && + correction_matrix_twophase_.size() == 2); + + std::lock_guard guard(cell_mutex_); + for (unsigned i = 0; i < Tdim; i++) { + correction_matrix_twophase_[phase].block(0, i * nnodes_, nnodes_, + nnodes_) += + shapefn * grad_shapefn.col(i).transpose() * multiplier * pvolume; + } +} \ No newline at end of file diff --git a/include/git.cc.in b/include/git.cc.in index d17df1900..e1383ede3 100644 --- a/include/git.cc.in +++ b/include/git.cc.in @@ -10,4 +10,4 @@ std::string GitMetadata::CommitDate() { return "@GIT_COMMIT_DATE_ISO8601@"; } std::string GitMetadata::CommitSubject() { return "@GIT_COMMIT_SUBJECT@"; } std::string GitMetadata::CommitBody() { return @GIT_COMMIT_BODY@; } std::string GitMetadata::Describe() { return "@GIT_DESCRIBE@"; } -// clang-format on +// clang-format on \ No newline at end of file diff --git a/include/io/io_mesh.h b/include/io/io_mesh.h index 4150af5b0..339839983 100644 --- a/include/io/io_mesh.h +++ b/include/io/io_mesh.h @@ -63,6 +63,17 @@ class IOMesh { virtual std::vector> read_particles_stresses( const std::string& particles_stresses) = 0; + //! Read particle scalar properties + //! \param[in] scalar_file file name with particle scalar properties + //! \retval Vector of particles scalar properties + virtual std::vector> + read_particles_scalar_properties(const std::string& scalar_file) = 0; + + //! Read pressure constraints file + //! \param[in] pressure_constraints_files file name with pressure constraints + virtual std::vector> read_pressure_constraints( + const std::string& _pressure_constraints_file) = 0; + //! Read nodal euler angles file //! \param[in] nodal_euler_angles_file file name with nodal id and respective //! euler angles diff --git a/include/io/io_mesh_ascii.h b/include/io/io_mesh_ascii.h index 433661546..e1b6031f9 100644 --- a/include/io/io_mesh_ascii.h +++ b/include/io/io_mesh_ascii.h @@ -51,6 +51,18 @@ class IOMeshAscii : public IOMesh { std::vector> read_particles_stresses( const std::string& particles_stresses) override; + //! Read particle scalar properties + //! \param[in] scalar_file file name with particle scalar properties + //! \retval Vector of particles scalar properties + std::vector> read_particles_scalar_properties( + const std::string& scalar_file) override; + + //! Read pressure constraints file + //! \param[in] pressure_constraints_files file name with pressure + //! constraints + std::vector> read_pressure_constraints( + const std::string& pressure_constraints_file) override; + //! Read nodal euler angles file //! \param[in] nodal_euler_angles_file file name with nodal id and respective //! euler angles diff --git a/include/io/io_mesh_ascii.tcc b/include/io/io_mesh_ascii.tcc index 0f09fdc1a..48c45b75b 100644 --- a/include/io/io_mesh_ascii.tcc +++ b/include/io/io_mesh_ascii.tcc @@ -246,6 +246,93 @@ std::vector> return stresses; } +//! Return particles scalar properties +template +std::vector> + mpm::IOMeshAscii::read_particles_scalar_properties( + const std::string& scalar_file) { + + // Particles scalar properties + std::vector> scalar_properties; + + // input file stream + std::fstream file; + file.open(scalar_file.c_str(), std::ios::in); + + try { + if (file.is_open() && file.good()) { + // Line + std::string line; + while (std::getline(file, line)) { + boost::algorithm::trim(line); + std::istringstream istream(line); + // ignore comment lines (# or !) or blank lines + if ((line.find('#') == std::string::npos) && + (line.find('!') == std::string::npos) && (line != "")) { + // ID + mpm::Index id; + // Scalar + double scalar; + while (istream.good()) { + // Read stream + istream >> id >> scalar; + scalar_properties.emplace_back(std::make_tuple(id, scalar)); + } + } + } + file.close(); + } + } catch (std::exception& exception) { + console_->error("Read particle {} #{}: {}\n", __FILE__, __LINE__, + exception.what()); + file.close(); + } + return scalar_properties; +} + +//! Read pressure constraints file +template +std::vector> + mpm::IOMeshAscii::read_pressure_constraints( + const std::string& pressure_constraints_file) { + // Particle pressure constraints + std::vector> constraints; + constraints.clear(); + + // input file stream + std::fstream file; + file.open(pressure_constraints_file.c_str(), std::ios::in); + + try { + if (file.is_open() && file.good()) { + // Line + std::string line; + while (std::getline(file, line)) { + boost::algorithm::trim(line); + std::istringstream istream(line); + // ignore comment lines (# or !) or blank lines + if ((line.find('#') == std::string::npos) && + (line.find('!') == std::string::npos) && (line != "")) { + // ID + mpm::Index id; + // Pressure + double pressure; + while (istream.good()) { + // Read stream + istream >> id >> pressure; + constraints.emplace_back(std::make_tuple(id, pressure)); + } + } + } + } + file.close(); + } catch (std::exception& exception) { + console_->error("Read pressure constraints: {}", exception.what()); + file.close(); + } + return constraints; +} + //! Return euler angles of nodes template std::map> @@ -270,12 +357,12 @@ std::map> // ignore comment lines (# or !) or blank lines if ((line.find('#') == std::string::npos) && (line.find('!') == std::string::npos) && (line != "")) { + // ID + mpm::Index id; + // Angles + Eigen::Matrix angles; while (istream.good()) { - // ID and read stream - mpm::Index id; istream >> id; - // Angles and ream stream - Eigen::Matrix angles; for (unsigned i = 0; i < Tdim; ++i) istream >> angles[i]; euler_angles.emplace(std::make_pair(id, angles)); } @@ -314,11 +401,11 @@ std::vector> // ignore comment lines (# or !) or blank lines if ((line.find('#') == std::string::npos) && (line.find('!') == std::string::npos) && (line != "")) { + // ID + mpm::Index id; + // Volume + double volume; while (istream.good()) { - // ID - mpm::Index id; - // Volume - double volume; // Read stream istream >> id >> volume; volumes.emplace_back(std::make_tuple(id, volume)); @@ -358,9 +445,9 @@ std::vector> // ignore comment lines (# or !) or blank lines if ((line.find('#') == std::string::npos) && (line.find('!') == std::string::npos) && (line != "")) { + // ID + mpm::Index pid, cid; while (istream.good()) { - // ID - mpm::Index pid, cid; // Read stream istream >> pid >> cid; particles_cells.emplace_back(std::array({pid, cid})); @@ -416,13 +503,13 @@ std::vector> // ignore comment lines (# or !) or blank lines if ((line.find('#') == std::string::npos) && (line.find('!') == std::string::npos) && (line != "")) { + // ID + mpm::Index id; + // Direction + unsigned dir; + // Velocity + double velocity; while (istream.good()) { - // ID - mpm::Index id; - // Direction - unsigned dir; - // Velocity - double velocity; // Read stream istream >> id >> dir >> velocity; constraints.emplace_back(std::make_tuple(id, dir, velocity)); @@ -462,15 +549,15 @@ std::vector> // ignore comment lines (# or !) or blank lines if ((line.find('#') == std::string::npos) && (line.find('!') == std::string::npos) && (line != "")) { + // ID + mpm::Index id; + // Direction + unsigned dir; + // Sign + int sign; + // Friction + double friction; while (istream.good()) { - // ID - mpm::Index id; - // Direction - unsigned dir; - // Sign - int sign; - // Friction - double friction; // Read stream istream >> id >> dir >> sign >> friction; constraints.emplace_back(std::make_tuple(id, dir, sign, friction)); @@ -509,13 +596,13 @@ std::vector> // ignore comment lines (# or !) or blank lines if ((line.find('#') == std::string::npos) && (line.find('!') == std::string::npos) && (line != "")) { + // ID + mpm::Index id; + // Direction + unsigned dir; + // Force + double force; while (istream.good()) { - // ID - mpm::Index id; - // Direction - unsigned dir; - // Force - double force; // Read stream istream >> id >> dir >> force; forces.emplace_back(std::make_tuple(id, dir, force)); diff --git a/include/io/logger.h b/include/io/logger.h index cf89ac8a3..ed1c7ec6b 100644 --- a/include/io/logger.h +++ b/include/io/logger.h @@ -44,6 +44,17 @@ struct Logger { // Create a logger for MPM Explicit MUSL static const std::shared_ptr mpm_explicit_musl_logger; + + // Create a logger for MPM Semi-implicit Navier Stokes + static const std::shared_ptr + mpm_semi_implicit_navier_stokes_logger; + + // Create a logger for MPM Explicit Two Phase + static const std::shared_ptr mpm_explicit_two_phase_logger; + + // Create a logger for MPM Semi-implicit Two Phase + static const std::shared_ptr + mpm_semi_implicit_two_phase_logger; }; } // namespace mpm diff --git a/include/io/partio_writer.h b/include/io/partio_writer.h index a15d97c12..65008746a 100644 --- a/include/io/partio_writer.h +++ b/include/io/partio_writer.h @@ -7,7 +7,7 @@ #include #include "data_types.h" -#include "hdf5_particle.h" +#include "pod_particle.h" namespace mpm::partio { @@ -15,7 +15,7 @@ namespace mpm::partio { //! \param[in] filename Mesh VTP file //! \param[in] particles HDF5 particles bool write_particles(const std::string& filename, - const std::vector& particles); + const std::vector& particles); } // namespace mpm::partio diff --git a/include/linear_solvers/assembler_base.h b/include/linear_solvers/assembler_base.h new file mode 100644 index 000000000..888023a4e --- /dev/null +++ b/include/linear_solvers/assembler_base.h @@ -0,0 +1,184 @@ +#ifndef MPM_ASSEMBLER_BASE_H_ +#define MPM_ASSEMBLER_BASE_H_ + +#include +#include +#include +#include +#include + +#include + +#include "mesh.h" +#include "node_base.h" + +namespace mpm { + +// Linear solver assembler base class +//! \brief Perform matrix assembly procedures +//! \details Build global matrices considering local element matrices +//! \tparam Tdim Dimension +template +class AssemblerBase { + public: + //! Constructor + //! \param[in] node_neighbourhood Number of node neighbourhood considered + AssemblerBase(unsigned node_neighbourhood) { + //! Global degrees of freedom + active_dof_ = 0; + //! Assign sparse row size + switch (node_neighbourhood) { + case 0: + sparse_row_size_ = (Tdim == 2) ? 9 : 27; + break; + case 1: + sparse_row_size_ = (Tdim == 2) ? 25 : 125; + break; + case 2: + sparse_row_size_ = (Tdim == 2) ? 49 : 343; + break; + default: + sparse_row_size_ = (Tdim == 2) ? 9 : 27; + } + } + + // Virtual destructor + virtual ~AssemblerBase() = default; + + //! Copy constructor + AssemblerBase(const AssemblerBase&) = default; + + //! Assignment operator + AssemblerBase& operator=(const AssemblerBase&) = default; + + //! Move constructor + AssemblerBase(AssemblerBase&&) = default; + + //! Assign mesh pointer + //! \param[in] mesh mesh pointer + void assign_mesh_pointer(const std::shared_ptr>& mesh) { + mesh_ = mesh; + } + + //! Create a pair between nodes and index in Matrix / Vector + virtual bool assign_global_node_indices(unsigned nactive_node, + unsigned nglobal_active_node) = 0; + + //! Assemble laplacian matrix + virtual Eigen::SparseMatrix& laplacian_matrix() = 0; + + //! Assemble laplacian matrix + virtual bool assemble_laplacian_matrix(double dt) = 0; + + //! Assemble poisson RHS vector + virtual Eigen::VectorXd& poisson_rhs_vector() = 0; + + //! Assemble poisson RHS vector + virtual bool assemble_poisson_right(double dt) = 0; + + //! Assign free surface node id + virtual void assign_free_surface( + const std::set& free_surface_id) = 0; + + //! Assign pressure constraints + virtual bool assign_pressure_constraints(double beta, + double current_time) = 0; + + //! Apply pressure constraints to poisson equation + virtual void apply_pressure_constraints() = 0; + + //! Return pressure increment + virtual Eigen::VectorXd& pressure_increment() = 0; + + //! Assign pressure increment + virtual void assign_pressure_increment( + const Eigen::VectorXd& pressure_increment) = 0; + + //! Return correction matrix + virtual Eigen::SparseMatrix& correction_matrix() = 0; + + //! Assemble corrector RHS + virtual bool assemble_corrector_right(double dt) = 0; + + //! Return the total size of global dof in all rank + virtual unsigned global_active_dof() = 0; + + //! Return number of total active_dof + virtual unsigned active_dof() { return active_dof_; }; + + //! Return a vector to map local (rank) index to global index + virtual std::vector rank_global_mapper() = 0; + + //! TwoPhase functions-------------------------------------------------------- + //! Assemble coefficient matrix for two-phase predictor + virtual bool assemble_predictor_left(double dt) { + throw std::runtime_error( + "Calling the base class function (assemble_predictor_left) in " + "AssemblerBase:: illegal operation!"); + return 0; + }; + + //! Return predictor coefficient LHS matrix + virtual Eigen::SparseMatrix& predictor_lhs_matrix(unsigned dir) { + throw std::runtime_error( + "Calling the base class function (predictor_lhs_matrix) in " + "AssemblerBase:: illegal operation!"); + }; + + //! Assemble RHS force vector for two-phase predictor + virtual bool assemble_predictor_right(double dt) { + throw std::runtime_error( + "Calling the base class function (assemble_predictor_right) in " + "AssemblerBase:: illegal operation!"); + return 0; + }; + + //! Return predictor RHS force vector + virtual Eigen::MatrixXd& predictor_rhs_vector() { + throw std::runtime_error( + "Calling the base class function (predictor_rhs_vector) in " + "AssemblerBase:: illegal operation!"); + }; + + //! Assign velocity constraints for matrix and vector + virtual bool assign_velocity_constraints() { + throw std::runtime_error( + "Calling the base class function (assign_velocity_constraints) in " + "AssemblerBase:: illegal operation!"); + return 0; + }; + + //! Apply velocity constraints for matrix and vector + virtual bool apply_velocity_constraints() { + throw std::runtime_error( + "Calling the base class function (apply_velocity_constraints) in " + "AssemblerBase:: illegal operation!"); + return 0; + }; + + //! Assign nodal intermediate acceleration + virtual void assign_intermediate_acceleration( + unsigned dim, Eigen::VectorXd acceleration_inter) { + throw std::runtime_error( + "Calling the base class function (assign_intermediate_acceleration) in " + "AssemblerBase:: illegal operation!"); + }; + + //! Return intermediate acceleration + virtual Eigen::MatrixXd& intermediate_acceleration() { + throw std::runtime_error( + "Calling the base class function (intermediate_acceleration) in " + "AssemblerBase:: illegal operation!"); + }; + + protected: + //! Number of total active_dof + unsigned active_dof_; + //! Mesh object + std::shared_ptr> mesh_; + //! Number of sparse matrix container size + unsigned sparse_row_size_; +}; +} // namespace mpm + +#endif // MPM_ASSEMBLER_BASE_H_ \ No newline at end of file diff --git a/include/linear_solvers/assembler_eigen_semi_implicit_navierstokes.h b/include/linear_solvers/assembler_eigen_semi_implicit_navierstokes.h new file mode 100644 index 000000000..44ffb8363 --- /dev/null +++ b/include/linear_solvers/assembler_eigen_semi_implicit_navierstokes.h @@ -0,0 +1,107 @@ +#ifndef MPM_ASSEMBLER_EIGEN_SEMI_IMPLICIT_NAVIERSTOKES_H_ +#define MPM_ASSEMBLER_EIGEN_SEMI_IMPLICIT_NAVIERSTOKES_H_ + +#include +#include + +// Speed log +#include "assembler_base.h" +#include "spdlog/spdlog.h" + +#include "mesh.h" + +namespace mpm { +template +class AssemblerEigenSemiImplicitNavierStokes : public AssemblerBase { + public: + //! Constructor + //! \param[in] node_neighbourhood Number of node neighbourhood considered + AssemblerEigenSemiImplicitNavierStokes(unsigned node_neighbourhood); + + //! Create a pair between nodes and index in Matrix / Vector + bool assign_global_node_indices(unsigned nactive_node, + unsigned nglobal_active_node) override; + + //! Return laplacian matrix + Eigen::SparseMatrix& laplacian_matrix() override { + return laplacian_matrix_; + } + + //! Assemble laplacian matrix + bool assemble_laplacian_matrix(double dt) override; + + //! Return poisson RHS vector + Eigen::VectorXd& poisson_rhs_vector() override { return poisson_rhs_vector_; } + + //! Assemble poisson RHS vector + bool assemble_poisson_right(double dt) override; + + //! Assign free surface node id + void assign_free_surface( + const std::set& free_surface_id) override { + free_surface_ = free_surface_id; + } + + //! Assign pressure constraints + bool assign_pressure_constraints(double beta, double current_time) override; + + //! Apply pressure constraints to poisson equation + void apply_pressure_constraints(); + + //! Return pressure increment + Eigen::VectorXd& pressure_increment() override { return pressure_increment_; } + + //! Assign pressure increment + void assign_pressure_increment( + const Eigen::VectorXd& pressure_increment) override { + pressure_increment_ = pressure_increment; + } + + //! Return correction matrix + Eigen::SparseMatrix& correction_matrix() override { + return correction_matrix_; + } + + //! Assemble corrector RHS + bool assemble_corrector_right(double dt) override; + + //! Return the total size of global dof in all rank + unsigned global_active_dof() override { return global_active_dof_; }; + + //! Return a vector to map local (rank) index to global index + std::vector rank_global_mapper() override { + return rank_global_mapper_; + }; + + protected: + //! number of nodes + using AssemblerBase::active_dof_; + //! Mesh object + using AssemblerBase::mesh_; + //! Number of sparse matrix container size + using AssemblerBase::sparse_row_size_; + //! Logger + std::unique_ptr console_; + //! Global node indices + std::vector global_node_indices_; + //! Laplacian matrix + Eigen::SparseMatrix laplacian_matrix_; + //! Poisson RHS vector + Eigen::VectorXd poisson_rhs_vector_; + //! Free surface + std::set free_surface_; + //! Pressure constraints + Eigen::SparseVector pressure_constraints_; + //! \delta p^(t+1) = p^(t+1) - beta * p^(t) + Eigen::VectorXd pressure_increment_; + //! correction_matrix + Eigen::SparseMatrix correction_matrix_; + //! Number of total active_dof in all rank + unsigned global_active_dof_; + //! Rank to Global mapper + std::vector rank_global_mapper_; +}; +} // namespace mpm + +#include "assembler_eigen_semi_implicit_navierstokes.tcc" +#endif // MPM_ASSEMBLER_EIGEN_SEMI_IMPLICIT_NAVIERSTOKES_H_ diff --git a/include/linear_solvers/assembler_eigen_semi_implicit_navierstokes.tcc b/include/linear_solvers/assembler_eigen_semi_implicit_navierstokes.tcc new file mode 100644 index 000000000..6a1cc5a46 --- /dev/null +++ b/include/linear_solvers/assembler_eigen_semi_implicit_navierstokes.tcc @@ -0,0 +1,284 @@ +//! Construct a semi-implicit eigen matrix assembler +template +mpm::AssemblerEigenSemiImplicitNavierStokes< + Tdim>::AssemblerEigenSemiImplicitNavierStokes(unsigned node_neighbourhood) + : mpm::AssemblerBase(node_neighbourhood) { + //! Logger + std::string logger = "AssemblerEigenSemiImplicitNavierStokes::"; + console_ = std::make_unique(logger, mpm::stdout_sink); +} + +//! Assign global node indices +template +bool mpm::AssemblerEigenSemiImplicitNavierStokes< + Tdim>::assign_global_node_indices(unsigned nactive_node, + unsigned nglobal_active_node) { + bool status = true; + try { + // Total number of active node (in a rank) and (rank) node indices + active_dof_ = nactive_node; + global_node_indices_ = mesh_->global_node_indices(); + +#ifdef USE_MPI + // Total number of active node (in all rank) + global_active_dof_ = nglobal_active_node; + + // Initialise mapping vector + rank_global_mapper_.resize(active_dof_); + + // Nodes container + const auto& nodes = mesh_->active_nodes(); + for (int counter = 0; counter < nodes.size(); counter++) { + // Assign get nodal global index + rank_global_mapper_[counter] = nodes[counter]->global_active_id(); + } +#endif + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Assemble Laplacian matrix +template +bool mpm::AssemblerEigenSemiImplicitNavierStokes< + Tdim>::assemble_laplacian_matrix(double dt) { + bool status = true; + try { + // Initialise Laplacian matrix + laplacian_matrix_.resize(active_dof_, active_dof_); + laplacian_matrix_.setZero(); + + // Reserve storage for sparse matrix + laplacian_matrix_.reserve( + Eigen::VectorXi::Constant(active_dof_, sparse_row_size_)); + + // Cell pointer + const auto& cells = mesh_->cells(); + + // Iterate over cells + mpm::Index cid = 0; + for (auto cell_itr = cells.cbegin(); cell_itr != cells.cend(); ++cell_itr) { + if ((*cell_itr)->status()) { + // Node ids in each cell + const auto nids = global_node_indices_.at(cid); + + // Laplacian element of cell + const auto cell_laplacian = (*cell_itr)->laplacian_matrix(); + + // Assemble global laplacian matrix + for (unsigned i = 0; i < nids.size(); ++i) { + for (unsigned j = 0; j < nids.size(); ++j) { + laplacian_matrix_.coeffRef(global_node_indices_.at(cid)(i), + global_node_indices_.at(cid)(j)) += + cell_laplacian(i, j); + } + } + + ++cid; + } + } + + laplacian_matrix_ *= dt; + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Assemble poisson right vector +template +bool mpm::AssemblerEigenSemiImplicitNavierStokes::assemble_poisson_right( + double dt) { + bool status = true; + try { + // Initialise Poisson RHS matrix + Eigen::SparseMatrix poisson_right_matrix; + poisson_right_matrix.resize(active_dof_, active_dof_ * Tdim); + poisson_right_matrix.setZero(); + + // Reserve storage for sparse matrix + poisson_right_matrix.reserve( + Eigen::VectorXi::Constant(active_dof_ * Tdim, sparse_row_size_)); + + // Cell pointer + const auto& cells = mesh_->cells(); + + // Iterate over cells + mpm::Index cid = 0; + for (auto cell_itr = cells.cbegin(); cell_itr != cells.cend(); ++cell_itr) { + if ((*cell_itr)->status()) { + // Node ids in each cell + const auto nids = global_node_indices_.at(cid); + + // Local Poisson RHS matrix + auto cell_poisson_right = (*cell_itr)->poisson_right_matrix(); + + // Assemble global poisson RHS matrix + for (unsigned i = 0; i < nids.size(); ++i) { + for (unsigned j = 0; j < nids.size(); ++j) { + for (unsigned k = 0; k < Tdim; ++k) { + poisson_right_matrix.coeffRef( + global_node_indices_.at(cid)(i), + global_node_indices_.at(cid)(j) + k * active_dof_) += + cell_poisson_right(i, j + k * nids.size()); + } + } + } + cid++; + } + } + + // Resize poisson right vector + poisson_rhs_vector_.resize(active_dof_); + poisson_rhs_vector_.setZero(); + + // Compute velocity + Eigen::MatrixXd fluid_velocity; + fluid_velocity.resize(active_dof_, Tdim); + fluid_velocity.setZero(); + + // Active nodes + const auto& active_nodes = mesh_->active_nodes(); + const unsigned fluid = mpm::ParticlePhase::SinglePhase; + unsigned node_index = 0; + + for (auto node_itr = active_nodes.cbegin(); node_itr != active_nodes.cend(); + ++node_itr) { + // Compute nodal intermediate force + fluid_velocity.row(node_index) = (*node_itr)->velocity(fluid).transpose(); + node_index++; + } + + // Resize fluid velocity + fluid_velocity.resize(active_dof_ * Tdim, 1); + + // Compute poisson RHS vector + poisson_rhs_vector_ = -poisson_right_matrix * fluid_velocity; + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Assemble corrector right matrix +template +bool mpm::AssemblerEigenSemiImplicitNavierStokes< + Tdim>::assemble_corrector_right(double dt) { + bool status = true; + try { + // Resize correction matrix + correction_matrix_.resize(active_dof_, active_dof_ * Tdim); + correction_matrix_.setZero(); + + // Reserve storage for sparse matrix + correction_matrix_.reserve( + Eigen::VectorXi::Constant(active_dof_ * Tdim, sparse_row_size_)); + + // Cell pointer + const auto& cells = mesh_->cells(); + + // Iterate over cells + unsigned cid = 0; + for (auto cell_itr = cells.cbegin(); cell_itr != cells.cend(); ++cell_itr) { + if ((*cell_itr)->status()) { + unsigned nnodes_per_cell = global_node_indices_.at(cid).size(); + auto cell_correction_matrix = (*cell_itr)->correction_matrix(); + for (unsigned k = 0; k < Tdim; k++) { + for (unsigned i = 0; i < nnodes_per_cell; i++) { + for (unsigned j = 0; j < nnodes_per_cell; j++) { + // Fluid + correction_matrix_.coeffRef( + global_node_indices_.at(cid)(i), + k * active_dof_ + global_node_indices_.at(cid)(j)) += + cell_correction_matrix(i, j + k * nnodes_per_cell); + } + } + } + cid++; + } + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Apply pressure constraints vector +template +void mpm::AssemblerEigenSemiImplicitNavierStokes< + Tdim>::apply_pressure_constraints() { + try { + // Modify poisson_rhs_vector_ + poisson_rhs_vector_ -= laplacian_matrix_ * pressure_constraints_; + + // Apply free surface + if (!free_surface_.empty()) { + const auto& nodes = mesh_->nodes(); + for (const auto& free_node : free_surface_) { + const auto column_index = nodes[free_node]->active_id(); + // Modify poisson_rhs_vector + poisson_rhs_vector_(column_index) = 0; + // Modify laplacian_matrix + laplacian_matrix_.row(column_index) *= 0; + laplacian_matrix_.col(column_index) *= 0; + laplacian_matrix_.coeffRef(column_index, column_index) = 1; + } + // Clear the vector + free_surface_.clear(); + } + + // Apply pressure constraints + for (Eigen::SparseVector::InnerIterator it(pressure_constraints_); + it; ++it) { + // Modify poisson_rhs_vector + poisson_rhs_vector_(it.index()) = it.value(); + // Modify laplacian_matrix + laplacian_matrix_.row(it.index()) *= 0; + laplacian_matrix_.col(it.index()) *= 0; + laplacian_matrix_.coeffRef(it.index(), it.index()) = 1; + } + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } +} + +//! Assign pressure constraints +template +bool mpm::AssemblerEigenSemiImplicitNavierStokes< + Tdim>::assign_pressure_constraints(double beta, double current_time) { + bool status = false; + try { + // Resize pressure constraints vector + pressure_constraints_.resize(active_dof_); + pressure_constraints_.reserve(int(0.5 * active_dof_)); + + // Nodes container + const auto& nodes = mesh_->active_nodes(); + // Iterate over nodes to get pressure constraints + for (auto node = nodes.cbegin(); node != nodes.cend(); ++node) { + // Assign total pressure constraint + const double pressure_constraint = + (*node)->pressure_constraint(0, current_time); + + // Check if there is a pressure constraint + if (pressure_constraint != std::numeric_limits::max()) { + // Insert the pressure constraints + pressure_constraints_.insert((*node)->active_id()) = + pressure_constraint; + } + } + status = true; + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return status; +} diff --git a/include/linear_solvers/assembler_eigen_semi_implicit_twophase.h b/include/linear_solvers/assembler_eigen_semi_implicit_twophase.h new file mode 100644 index 000000000..7902c1df3 --- /dev/null +++ b/include/linear_solvers/assembler_eigen_semi_implicit_twophase.h @@ -0,0 +1,92 @@ +#ifndef MPM_ASSEMBLER_EIGEN_SEMI_IMPLICIT_TWOPHASE_H_ +#define MPM_ASSEMBLER_EIGEN_SEMI_IMPLICIT_TWOPHASE_H_ + +#include +#include + +// Speed log +#include "assembler_eigen_semi_implicit_navierstokes.h" +#include "mesh.h" +#include "spdlog/spdlog.h" + +namespace mpm { +template +class AssemblerEigenSemiImplicitTwoPhase + : public AssemblerEigenSemiImplicitNavierStokes { + public: + //! Constructor + //! \param[in] node_neighbourhood Number of node neighbourhood considered + AssemblerEigenSemiImplicitTwoPhase(unsigned node_neighbourhood); + + //! Assemble coefficient matrix for two-phase predictor + bool assemble_predictor_left(double dt) override; + + //! Return predictor coefficient LHS matrix + Eigen::SparseMatrix& predictor_lhs_matrix(unsigned dir) override { + return predictor_lhs_matrix_.at(dir); + } + + //! Assemble predictor RHS force vector + bool assemble_predictor_right(double dt) override; + + //! Return predictor RHS vector + Eigen::MatrixXd& predictor_rhs_vector() override { + return predictor_rhs_vector_; + } + + //! Assign intermediate acceleration + void assign_intermediate_acceleration( + unsigned dir, Eigen::VectorXd acceleration_inter) override { + intermediate_acceleration_.col(dir) = acceleration_inter; + } + + //! Return intermediate acceleration + Eigen::MatrixXd& intermediate_acceleration() override { + return intermediate_acceleration_; + } + + //! Assemble poisson RHS vector + bool assemble_poisson_right(double dt) override; + + //! Assemble corrector RHS + bool assemble_corrector_right(double dt) override; + + //! Assign velocity constraints + bool assign_velocity_constraints() override; + + //! Apply velocity constraints for matrix and vector + bool apply_velocity_constraints() override; + + //! Assign pressure constraints + bool assign_pressure_constraints(double beta, double current_time) override; + + protected: + //! number of nodes + using AssemblerBase::active_dof_; + //! Mesh object + using AssemblerBase::mesh_; + //! Number of sparse matrix container size + using AssemblerBase::sparse_row_size_; + //! Global node indices + using AssemblerEigenSemiImplicitNavierStokes::global_node_indices_; + //! Poisson RHS vector + using AssemblerEigenSemiImplicitNavierStokes::poisson_rhs_vector_; + //! Pressure constraints + using AssemblerEigenSemiImplicitNavierStokes::pressure_constraints_; + //! Correction_matrix + using AssemblerEigenSemiImplicitNavierStokes::correction_matrix_; + //! Logger + std::unique_ptr console_; + //! Coefficient matrix for two-phase predictor + std::map> predictor_lhs_matrix_; + //! RHS vector for two-phase predictor + Eigen::MatrixXd predictor_rhs_vector_; + //! Intermediate acceleration vector (each column represent one direction) + Eigen::MatrixXd intermediate_acceleration_; + //! Velocity constraints + Eigen::SparseMatrix velocity_constraints_; +}; +} // namespace mpm + +#include "assembler_eigen_semi_implicit_twophase.tcc" +#endif // MPM_ASSEMBLER_EIGEN_SEMI_IMPLICIT_TWOPHASE_H_ diff --git a/include/linear_solvers/assembler_eigen_semi_implicit_twophase.tcc b/include/linear_solvers/assembler_eigen_semi_implicit_twophase.tcc new file mode 100644 index 000000000..6b66dc2b2 --- /dev/null +++ b/include/linear_solvers/assembler_eigen_semi_implicit_twophase.tcc @@ -0,0 +1,397 @@ +//! Construct a semi-implicit eigen matrix assembler +template +mpm::AssemblerEigenSemiImplicitTwoPhase< + Tdim>::AssemblerEigenSemiImplicitTwoPhase(unsigned node_neighbourhood) + : mpm::AssemblerEigenSemiImplicitNavierStokes(node_neighbourhood) { + //! Logger + std::string logger = "AssemblerEigenSemiImplicitTwoPhase::"; + console_ = std::make_unique(logger, mpm::stdout_sink); +} + +//! Assemble coefficient matrix for two-phase predictor +template +bool mpm::AssemblerEigenSemiImplicitTwoPhase::assemble_predictor_left( + double dt) { + bool status = true; + try { + // Loop over three direction + for (unsigned dir = 0; dir < Tdim; dir++) { + // Initialise coefficient_matrix + Eigen::SparseMatrix coefficient_matrix; + coefficient_matrix.setZero(); + + // Resize coefficient matrix + coefficient_matrix.resize(2 * active_dof_, 2 * active_dof_); + + // Reserve storage for sparse matrix + coefficient_matrix.reserve( + Eigen::VectorXi::Constant(2 * active_dof_, 2 * sparse_row_size_)); + + // Cell pointer + const auto& cells = mesh_->cells(); + + // Iterate over cells for drag force coefficient + mpm::Index cid = 0; + for (auto cell_itr = cells.cbegin(); cell_itr != cells.cend(); + ++cell_itr) { + if ((*cell_itr)->status()) { + // Node ids in each cell + const auto nids = global_node_indices_.at(cid); + // Local drag matrix + auto cell_drag_matrix = (*cell_itr)->drag_matrix(dir); + // Assemble global coefficient matrix + for (unsigned i = 0; i < nids.size(); ++i) { + for (unsigned j = 0; j < nids.size(); ++j) { + coefficient_matrix.coeffRef(nids(i) + active_dof_, nids(j)) += + -cell_drag_matrix(i, j) * dt; + coefficient_matrix.coeffRef(nids(i) + active_dof_, + nids(j) + active_dof_) += + cell_drag_matrix(i, j) * dt; + } + } + ++cid; + } + } + + // Active nodes pointer + const auto& nodes = mesh_->active_nodes(); + // Iterate over cells for mass coefficient + for (auto node_itr = nodes.cbegin(); node_itr != nodes.cend(); + ++node_itr) { + // Id for active node + auto active_id = (*node_itr)->active_id(); + // Assemble global coefficient matrix for solid mass + coefficient_matrix.coeffRef(active_id, active_id) += + (*node_itr)->mass(mpm::NodePhase::NSolid); + // Assemble global coefficient matrix for liquid mass + coefficient_matrix.coeffRef(active_id + active_dof_, + active_id + active_dof_) += + (*node_itr)->mass(mpm::NodePhase::NLiquid); + coefficient_matrix.coeffRef(active_id, active_id + active_dof_) += + (*node_itr)->mass(mpm::NodePhase::NLiquid); + } + + // Add coefficient matrix to map + if (predictor_lhs_matrix_.find(dir) != predictor_lhs_matrix_.end()) + predictor_lhs_matrix_.erase(dir); + + predictor_lhs_matrix_.insert( + std::make_pair>( + static_cast(dir), + static_cast>(coefficient_matrix))); + } + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Assemble predictor RHS force vector +template +bool mpm::AssemblerEigenSemiImplicitTwoPhase::assemble_predictor_right( + double dt) { + bool status = true; + try { + // Resize intermediate velocity vector + intermediate_acceleration_.resize(active_dof_ * 2, Tdim); + intermediate_acceleration_.setZero(); + + // Resize force vector + predictor_rhs_vector_.resize(active_dof_ * 2, Tdim); + predictor_rhs_vector_.setZero(); + + // Active nodes pointer + const auto& nodes = mesh_->active_nodes(); + // Iterate over nodes + mpm::Index nid = 0; + for (auto node_itr = nodes.cbegin(); node_itr != nodes.cend(); ++node_itr) { + // Compute nodal intermediate force + const Eigen::Matrix mixture_force = + (*node_itr)->external_force(mpm::NodePhase::NMixture) + + (*node_itr)->internal_force(mpm::NodePhase::NMixture); + + const Eigen::Matrix drag_force = + (*node_itr)->drag_force_coefficient().cwiseProduct( + (*node_itr)->velocity(mpm::NodePhase::NLiquid) - + (*node_itr)->velocity(mpm::NodePhase::NSolid)); + + const Eigen::Matrix fluid_force = + (*node_itr)->external_force(mpm::NodePhase::NLiquid) + + (*node_itr)->internal_force(mpm::NodePhase::NLiquid) - drag_force; + + // Assemble intermediate force vector + predictor_rhs_vector_.row(nid) = mixture_force.transpose(); + predictor_rhs_vector_.row(nid + active_dof_) = fluid_force.transpose(); + ++nid; + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Assemble poisson right vector +template +bool mpm::AssemblerEigenSemiImplicitTwoPhase::assemble_poisson_right( + double dt) { + bool status = true; + try { + // Initialise Poisson RHS matrix + Eigen::SparseMatrix solid_poisson_right_matrix, + liquid_poisson_right_matrix; + + // Resize poisson right matrix for solid + solid_poisson_right_matrix.resize(active_dof_, active_dof_ * Tdim); + solid_poisson_right_matrix.setZero(); + // Resize poisson right matrix for liquid + liquid_poisson_right_matrix.resize(active_dof_, active_dof_ * Tdim); + liquid_poisson_right_matrix.setZero(); + + // Reserve storage for sparse matrix + solid_poisson_right_matrix.reserve( + Eigen::VectorXi::Constant(active_dof_ * Tdim, sparse_row_size_)); + liquid_poisson_right_matrix.reserve( + Eigen::VectorXi::Constant(active_dof_ * Tdim, sparse_row_size_)); + + // Cell pointer + const auto& cells = mesh_->cells(); + + // Iterate over cells + mpm::Index cid = 0; + for (auto cell_itr = cells.cbegin(); cell_itr != cells.cend(); ++cell_itr) { + if ((*cell_itr)->status()) { + // Node ids in each cell + const auto nids = global_node_indices_.at(cid); + + // Local Poisson RHS matrix for solid + auto cell_poisson_right_solid = + (*cell_itr)->poisson_right_matrix(mpm::NodePhase::NSolid); + // Local Poisson RHS matrix for liquid + auto cell_poisson_right_liquid = + (*cell_itr)->poisson_right_matrix(mpm::NodePhase::NLiquid); + + // Assemble global poisson RHS matrix + for (unsigned i = 0; i < nids.size(); ++i) { + for (unsigned j = 0; j < nids.size(); ++j) { + for (unsigned k = 0; k < Tdim; ++k) { + solid_poisson_right_matrix.coeffRef( + global_node_indices_.at(cid)(i), + global_node_indices_.at(cid)(j) + k * active_dof_) += + cell_poisson_right_solid(i, j + k * nids.size()); + liquid_poisson_right_matrix.coeffRef( + global_node_indices_.at(cid)(i), + global_node_indices_.at(cid)(j) + k * active_dof_) += + cell_poisson_right_liquid(i, j + k * nids.size()); + } + } + } + cid++; + } + } + + // Resize poisson right vector + poisson_rhs_vector_.resize(active_dof_); + poisson_rhs_vector_.setZero(); + + // Compute intermediate solid and liquid velocity + Eigen::MatrixXd solid_velocity, liquid_velocity; + solid_velocity.resize(active_dof_, Tdim); + solid_velocity.setZero(); + liquid_velocity.resize(active_dof_, Tdim); + liquid_velocity.setZero(); + + // Active nodes + const auto& active_nodes = mesh_->active_nodes(); + unsigned node_index = 0; + + for (auto node_itr = active_nodes.cbegin(); node_itr != active_nodes.cend(); + ++node_itr) { + // Compute nodal intermediate force + solid_velocity.row(node_index) = + (*node_itr)->velocity(mpm::NodePhase::NSolid).transpose(); + liquid_velocity.row(node_index) = + (*node_itr)->velocity(mpm::NodePhase::NLiquid).transpose(); + node_index++; + } + + // Resize velocity vectors + solid_velocity.resize(active_dof_ * Tdim, 1); + liquid_velocity.resize(active_dof_ * Tdim, 1); + + // Compute poisson RHS vector + poisson_rhs_vector_ = -(solid_poisson_right_matrix * solid_velocity) - + (liquid_poisson_right_matrix * liquid_velocity); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Assemble corrector right matrix +template +bool mpm::AssemblerEigenSemiImplicitTwoPhase::assemble_corrector_right( + double dt) { + bool status = true; + try { + // Resize correction matrix + correction_matrix_.resize(2 * active_dof_, active_dof_ * Tdim); + correction_matrix_.setZero(); + + // Reserve storage for sparse matrix + correction_matrix_.reserve( + Eigen::VectorXi::Constant(active_dof_ * Tdim, 2 * sparse_row_size_)); + + // Cell pointer + const auto& cells = mesh_->cells(); + + // Iterate over cells + unsigned cid = 0; + for (auto cell_itr = cells.cbegin(); cell_itr != cells.cend(); ++cell_itr) { + if ((*cell_itr)->status()) { + // Number of nodes in cell + unsigned nnodes_per_cell = global_node_indices_.at(cid).size(); + // Local correction matrix for solid + auto correction_matrix_solid = + (*cell_itr)->correction_matrix(mpm::NodePhase::NSolid); + // Local correction matrix for liquid + auto coefficient_matrix_liquid = + (*cell_itr)->correction_matrix(mpm::NodePhase::NLiquid); + for (unsigned k = 0; k < Tdim; k++) { + for (unsigned i = 0; i < nnodes_per_cell; i++) { + for (unsigned j = 0; j < nnodes_per_cell; j++) { + // Solid phase + correction_matrix_.coeffRef( + global_node_indices_.at(cid)(i), + k * active_dof_ + global_node_indices_.at(cid)(j)) += + correction_matrix_solid(i, j + k * nnodes_per_cell); + // Liquid phase + correction_matrix_.coeffRef( + global_node_indices_.at(cid)(i) + active_dof_, + k * active_dof_ + global_node_indices_.at(cid)(j)) += + coefficient_matrix_liquid(i, j + k * nnodes_per_cell); + } + } + } + cid++; + } + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Assign pressure constraints +template +bool mpm::AssemblerEigenSemiImplicitTwoPhase::assign_pressure_constraints( + double beta, double current_time) { + bool status = false; + try { + // Resize pressure constraints vector + pressure_constraints_.setZero(); + pressure_constraints_.data().squeeze(); + pressure_constraints_.resize(active_dof_); + pressure_constraints_.reserve(int(0.5 * active_dof_)); + + // Nodes container + const auto& nodes = mesh_->active_nodes(); + // Iterate over nodes to get pressure constraints + for (auto node = nodes.cbegin(); node != nodes.cend(); ++node) { + // Assign total pressure constraint + const double pressure_constraint = + (*node)->pressure_constraint(mpm::NodePhase::NLiquid, current_time); + + // Check if there is a pressure constraint + if (pressure_constraint != std::numeric_limits::max()) { + // Insert the pressure constraints + pressure_constraints_.insert((*node)->active_id()) = + (1 - beta) * pressure_constraint; + } + } + status = true; + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return status; +} + +//! Assign velocity constraints +template +bool mpm::AssemblerEigenSemiImplicitTwoPhase< + Tdim>::assign_velocity_constraints() { + bool status = false; + try { + // Initialise constraints matrix from triplet + std::vector> triplet_list; + // Nodes container + const auto& nodes = mesh_->active_nodes(); + // Iterate over nodes + for (auto node = nodes.cbegin(); node != nodes.cend(); ++node) { + // Get velocity constraints + const auto& velocity_constraints = (*node)->velocity_constraints(); + // Assign constraints matrix + for (const auto constraint : velocity_constraints) { + // Insert constraint to the matrix + triplet_list.push_back(Eigen::Triplet( + (constraint).first / Tdim * active_dof_ + (*node)->active_id(), + (constraint).first % Tdim, (constraint).second)); + } + } + // Reserve the storage for the velocity constraints matrix + velocity_constraints_.setZero(); + velocity_constraints_.data().squeeze(); + velocity_constraints_.resize(active_dof_ * 2, Tdim); + velocity_constraints_.reserve(Eigen::VectorXi::Constant( + Tdim, triplet_list.size() + sparse_row_size_)); + // Assemble the velocity constraints matrix + velocity_constraints_.setFromTriplets(triplet_list.begin(), + triplet_list.end()); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return status; +} + +//! Apply velocity constraints to preditor RHS vector +template +bool mpm::AssemblerEigenSemiImplicitTwoPhase< + Tdim>::apply_velocity_constraints() { + bool status = false; + try { + // Modify the force vector(b = b - A * bc) + for (unsigned dir = 0; dir < Tdim; dir++) { + predictor_rhs_vector_.col(dir) -= + predictor_lhs_matrix_.at(dir) * velocity_constraints_.col(dir); + + // Iterate over velocity constraints (non-zero elements) + for (unsigned j = 0; j < velocity_constraints_.outerSize(); ++j) { + for (Eigen::SparseMatrix::InnerIterator itr( + velocity_constraints_, j); + itr; ++itr) { + // Check direction + if (itr.col() == dir) { + // Assign 0 to specified column + predictor_lhs_matrix_.at(dir).col(itr.row()) *= 0; + // Assign 0 to specified row + predictor_lhs_matrix_.at(dir).row(itr.row()) *= 0; + // Assign 1 to diagnal element + predictor_lhs_matrix_.at(dir).coeffRef(itr.row(), itr.row()) = 1.0; + + predictor_rhs_vector_(itr.row(), itr.col()) = 0.; + } + } + } + } + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return status; +} \ No newline at end of file diff --git a/include/linear_solvers/iterative_eigen.h b/include/linear_solvers/iterative_eigen.h new file mode 100644 index 000000000..eb77e73ba --- /dev/null +++ b/include/linear_solvers/iterative_eigen.h @@ -0,0 +1,64 @@ +#ifndef MPM_ITERATIVE_EIGEN_H_ +#define MPM_ITERATIVE_EIGEN_H_ + +#include + +#include "factory.h" +#include "solver_base.h" +#include +#include +#include + +namespace mpm { + +//! MPM Iterative Eigen solver class +//! \brief Iterative linear sparse matrix solver class using Eigen library +template +class IterativeEigen : public SolverBase { + public: + //! Constructor + //! \param[in] max_iter Maximum number of iterations + //! \param[in] tolerance Tolerance for solver to achieve convergence + IterativeEigen(unsigned max_iter, double tolerance) + : mpm::SolverBase(max_iter, tolerance) { + //! Logger + std::string logger = "EigenIterativeSolver::"; + console_ = std::make_unique(logger, mpm::stdout_sink); + }; + + //! Destructor + ~IterativeEigen(){}; + + //! Matrix solver with default initial guess + Eigen::VectorXd solve(const Eigen::SparseMatrix& A, + const Eigen::VectorXd& b) override; + + //! Return the type of solver + std::string solver_type() const { return "Eigen"; } + + //! Assign global active dof + void assign_global_active_dof(unsigned global_active_dof) override {} + + //! Assign rank to global mapper + void assign_rank_global_mapper(std::vector rank_global_mapper) override { + } + + protected: + //! Solver type + using SolverBase::sub_solver_type_; + //! Preconditioner type + using SolverBase::preconditioner_type_; + //! Maximum number of iterations + using SolverBase::max_iter_; + //! Tolerance + using SolverBase::tolerance_; + //! Verbosity + using SolverBase::verbosity_; + //! Logger + std::unique_ptr console_; +}; +} // namespace mpm + +#include "iterative_eigen.tcc" + +#endif // MPM_ITERATIVE_EIGEN_H_ diff --git a/include/linear_solvers/iterative_eigen.tcc b/include/linear_solvers/iterative_eigen.tcc new file mode 100644 index 000000000..f25d7fc4f --- /dev/null +++ b/include/linear_solvers/iterative_eigen.tcc @@ -0,0 +1,93 @@ +//! Conjugate Gradient with default initial guess +template +Eigen::VectorXd mpm::IterativeEigen::solve( + const Eigen::SparseMatrix& A, const Eigen::VectorXd& b) { + Eigen::VectorXd x; + try { + + // Solver start + auto solver_begin = std::chrono::steady_clock::now(); + if (verbosity_ > 0) + console_->info("Type: \"{}\", Preconditioner: \"{}\", Begin!", + sub_solver_type_, preconditioner_type_); + + if (verbosity_ == 3) { + std::cout << "Coefficient Matrix A: " << A << std::endl; + std::cout << "RHS Vector b: " << b << std::endl; + } + + if (sub_solver_type_ == "cg") { + Eigen::ConjugateGradient> solver; + + solver.setMaxIterations(max_iter_); + solver.setTolerance(tolerance_); + solver.compute(A); + + x = solver.solve(b); + + if (verbosity_ >= 1) { + std::cout << "#iterations: " << solver.iterations() << std::endl; + std::cout << "estimated error: " << solver.error() << std::endl; + } + + if (solver.info() != Eigen::Success) { + throw std::runtime_error("Fail to solve linear systems!\n"); + } + + } else if (sub_solver_type_ == "lscg") { + Eigen::LeastSquaresConjugateGradient> solver; + + solver.setMaxIterations(max_iter_); + solver.setTolerance(tolerance_); + solver.compute(A); + + x = solver.solve(b); + + if (verbosity_ >= 1) { + std::cout << "#iterations: " << solver.iterations() << std::endl; + std::cout << "estimated error: " << solver.error() << std::endl; + } + + if (solver.info() != Eigen::Success) { + throw std::runtime_error("Fail to solve linear systems!\n"); + } + + } else if (sub_solver_type_ == "bicgstab") { + Eigen::BiCGSTAB> solver; + + solver.setMaxIterations(max_iter_); + solver.setTolerance(tolerance_); + solver.compute(A); + + x = solver.solve(b); + + if (verbosity_ >= 1) { + std::cout << "#iterations: " << solver.iterations() << std::endl; + std::cout << "estimated error: " << solver.error() << std::endl; + } + + if (solver.info() != Eigen::Success) { + throw std::runtime_error("Fail to solve linear systems!\n"); + } + } else { + throw std::runtime_error( + "Sub solver type is not available! Available sub solver type " + "implemented in IterativeEigen class are: \"cg\", \"lscg\", and " + "\"bicgstab\".\n"); + } + + // Solver End + auto solver_end = std::chrono::steady_clock::now(); + if (verbosity_ > 0) + console_->info( + "Type: \"{}\", Preconditioner: \"{}\", End! Duration: {} ms.", + sub_solver_type_, preconditioner_type_, + std::chrono::duration_cast(solver_end - + solver_begin) + .count()); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return x; +} \ No newline at end of file diff --git a/include/linear_solvers/krylov_petsc.h b/include/linear_solvers/krylov_petsc.h new file mode 100644 index 000000000..43445cc24 --- /dev/null +++ b/include/linear_solvers/krylov_petsc.h @@ -0,0 +1,82 @@ +#ifndef MPM_KRYLOV_PETSC_H_ +#define MPM_KRYLOV_PETSC_H_ + +#include + +#include "factory.h" +#include "mpi.h" +#include "solver_base.h" +#include + +#ifdef USE_PETSC +#include +#endif + +namespace mpm { + +//! MPM Eigen CG class +//! \brief Conjugate Gradient solver class using Eigen +template +class KrylovPETSC : public SolverBase { + public: + //! Constructor + //! \param[in] max_iter Maximum number of iterations + //! \param[in] tolerance Tolerance for solver to achieve convergence + KrylovPETSC(unsigned max_iter, double tolerance) + : mpm::SolverBase(max_iter, tolerance) { + //! Logger + std::string logger = "PETSCKrylovSolver::"; + console_ = std::make_unique(logger, mpm::stdout_sink); + +#ifdef USE_PETSC + abs_tolerance_ = PETSC_DEFAULT; + div_tolerance_ = PETSC_DEFAULT; +#endif + }; + + //! Destructor + ~KrylovPETSC() {} + + //! Matrix solver with default initial guess + Eigen::VectorXd solve(const Eigen::SparseMatrix& A, + const Eigen::VectorXd& b) override; + + //! Return the type of solver + std::string solver_type() const { return "PETSC"; } + + //! Assign global active dof + void assign_global_active_dof(unsigned global_active_dof) override { + global_active_dof_ = global_active_dof; + }; + + //! Assign rank to global mapper + void assign_rank_global_mapper(std::vector rank_global_mapper) override { + rank_global_mapper_ = rank_global_mapper; + }; + + protected: + //! Solver type + using SolverBase::sub_solver_type_; + //! Preconditioner type + using SolverBase::preconditioner_type_; + //! Maximum number of iterations + using SolverBase::max_iter_; + //! Relative tolerance + using SolverBase::tolerance_; + //! Absolute tolerance + using SolverBase::abs_tolerance_; + //! Divergence tolerance + using SolverBase::div_tolerance_; + //! Verbosity + using SolverBase::verbosity_; + //! Logger + std::unique_ptr console_; + //! Global active dof + unsigned global_active_dof_; + //! Rank global Mapper + std::vector rank_global_mapper_; +}; +} // namespace mpm + +#include "krylov_petsc.tcc" +#endif // MPM_KRYLOV_PETSC_H_ diff --git a/include/linear_solvers/krylov_petsc.tcc b/include/linear_solvers/krylov_petsc.tcc new file mode 100644 index 000000000..70ba48be1 --- /dev/null +++ b/include/linear_solvers/krylov_petsc.tcc @@ -0,0 +1,193 @@ +//! Conjugate Gradient with default initial guess +template +Eigen::VectorXd mpm::KrylovPETSC::solve( + const Eigen::SparseMatrix& A, const Eigen::VectorXd& b) { + //! Initialize solution vector x + Eigen::VectorXd x(b.size()); + try { +#if USE_PETSC + + // Initialise MPI mpi_rank and size + int mpi_rank = 0; + int mpi_size = 1; + + // Get MPI mpi_rank + MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); + // Get number of MPI mpi_ranks + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); + + // Solver start + auto solver_begin = std::chrono::steady_clock::now(); + if (verbosity_ > 0 && mpi_rank == 0) + console_->info("Type: \"{}\", Preconditioner: \"{}\", Begin!", + sub_solver_type_, preconditioner_type_); + + // Initialize PETSC matrix, vectors, and parameters + KSP solver; + Mat petsc_A; + Vec petsc_b, petsc_x; + KSPConvergedReason reason; + + // Initialize vector b across the ranks + VecCreateMPI(MPI_COMM_WORLD, PETSC_DECIDE, global_active_dof_, &petsc_b); + VecDuplicate(petsc_b, &petsc_x); + + // Initialize Matrix A across the ranks + MatCreateAIJ(MPI_COMM_WORLD, PETSC_DECIDE, PETSC_DECIDE, global_active_dof_, + global_active_dof_, 0, NULL, 0, NULL, &petsc_A); + MatSetOption(petsc_A, MAT_NEW_NONZERO_ALLOCATION_ERR, PETSC_FALSE); + + // Copying Eigen vector b to petsc b vector + VecSetValues(petsc_b, rank_global_mapper_.size(), + rank_global_mapper_.data(), b.data(), ADD_VALUES); + VecAssemblyBegin(petsc_b); + VecAssemblyEnd(petsc_b); + + // Output assembled right-hand-side vector in each rank + PetscViewer viewer; + if (verbosity_ == 3) { + PetscViewerASCIIGetStdout(MPI_COMM_WORLD, &viewer); + VecView(petsc_b, viewer); + } + + // Copying Eigen matrix A to petsc A matrix + for (int k = 0; k < A.outerSize(); ++k) { + for (Eigen::SparseMatrix::InnerIterator it(A, k); it; ++it) { + MatSetValue(petsc_A, rank_global_mapper_[it.row()], + rank_global_mapper_[k], it.value(), ADD_VALUES); + } + } + MatAssemblyBegin(petsc_A, MAT_FINAL_ASSEMBLY); + MatAssemblyEnd(petsc_A, MAT_FINAL_ASSEMBLY); + + // Output coefficient matrix A + if (verbosity_ == 3) { + PetscViewerASCIIGetStdout(MPI_COMM_WORLD, &viewer); + MatView(petsc_A, viewer); + } + + // Initiate PETSC solver + KSPCreate(MPI_COMM_WORLD, &solver); + KSPSetOperators(solver, petsc_A, petsc_A); + + // Set solver_type + if (sub_solver_type_ == "cg") { + KSPSetType(solver, KSPCG); + KSPCGSetType(solver, KSP_CG_SYMMETRIC); + } else if (sub_solver_type_ == "gmres") + KSPSetType(solver, KSPGMRES); + else if (sub_solver_type_ == "bicgstab") + KSPSetType(solver, KSPBCGS); + else if (sub_solver_type_ == "lsqr") + KSPSetType(solver, KSPLSQR); + else { + if (verbosity_ > 0 && mpi_rank == 0) + console_->warn( + "Sub solver type is not available! Using \"gmres\" as default " + "type. Available sub solver type implemented in KrylovPETSC " + "class are: \"cg\", \"gmres\", \"lsqr\", and " + "\"bicgstab\"."); + } + + // Set tolerance + KSPSetTolerances(solver, tolerance_, abs_tolerance_, div_tolerance_, + max_iter_); + + // Set preconditioner + if (preconditioner_type_ != "none") { + PC pc; + KSPSetInitialGuessNonzero(solver, PETSC_TRUE); + KSPGetPC(solver, &pc); + PCFactorSetShiftType(pc, MAT_SHIFT_POSITIVE_DEFINITE); + if (preconditioner_type_ == "jacobi") PCSetType(pc, PCJACOBI); + if (preconditioner_type_ == "bjacobi") PCSetType(pc, PCBJACOBI); + if (preconditioner_type_ == "pbjacobi") PCSetType(pc, PCPBJACOBI); + if (preconditioner_type_ == "asm") PCSetType(pc, PCASM); + if (preconditioner_type_ == "eisenstat") PCSetType(pc, PCEISENSTAT); + if (preconditioner_type_ == "icc") PCSetType(pc, PCICC); + } + + // Solve linear system of equation x = A^(-1) b + KSPSolve(solver, petsc_b, petsc_x); + KSPGetConvergedReason(solver, &reason); + + // Print residual in each iteration + if (verbosity_ >= 1) { + PetscViewerAndFormat* vf; + PetscViewerAndFormatCreate(PETSC_VIEWER_STDOUT_WORLD, + PETSC_VIEWER_DEFAULT, &vf); + PetscInt its; + KSPGetIterationNumber(solver, &its); + PetscPrintf(PETSC_COMM_WORLD, "\nConvergence in %d iterations.\n", + (int)its); + PetscReal rnorm; + if (verbosity_ >= 2) { + for (int i = 0; i < its; i++) { + KSPMonitorTrueResidualNorm(solver, i, rnorm, vf); + } + } + } + + // Warn if solver does not converge + if (reason < 0) { + PetscPrintf(MPI_COMM_WORLD, + "\nKrylov PETSC solver \"%s\" with \"%s\" preconditioner " + "DIVERGED, try to modify the preconditioner, set tolerance " + "and maximum iteration.\n", + sub_solver_type_.c_str(), preconditioner_type_.c_str()); + } + + // Scatter and gather for cloning procedure + if (mpi_size > 1) { + // Initiate scatter arrays + VecScatter ctx; + Vec x_seq; + PetscScalar* x_data; + VecScatterCreateToAll(petsc_x, &ctx, &x_seq); + VecScatterBegin(ctx, petsc_x, x_seq, INSERT_VALUES, SCATTER_FORWARD); + VecScatterEnd(ctx, petsc_x, x_seq, INSERT_VALUES, SCATTER_FORWARD); + VecGetArray(x_seq, &x_data); + + // Copy petsc x to Eigen x + for (unsigned i = 0; i < x.size(); i++) { + const int global_index = rank_global_mapper_[i]; + x(i) = x_data[global_index]; + } + + // Destroy scatter arrays + VecRestoreArray(x_seq, &x_data); + VecScatterDestroy(&ctx); + VecDestroy(&x_seq); + } else { + PetscScalar value; + // Copy petsc x to Eigen x + for (unsigned i = 0; i < x.size(); i++) { + const int global_index = rank_global_mapper_[i]; + VecGetValues(petsc_x, 1, &global_index, &value); + x(i) = value; + } + } + + // Free work space + VecDestroy(&petsc_x); + VecDestroy(&petsc_b); + MatDestroy(&petsc_A); + KSPDestroy(&solver); + + // Solver End + auto solver_end = std::chrono::steady_clock::now(); + if (verbosity_ > 0 && mpi_rank == 0) + console_->info( + "Type: \"{}\", Preconditioner: \"{}\", End! Duration: {} ms.", + sub_solver_type_, preconditioner_type_, + std::chrono::duration_cast(solver_end - + solver_begin) + .count()); + +#endif + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + + return x; +} \ No newline at end of file diff --git a/include/linear_solvers/solver_base.h b/include/linear_solvers/solver_base.h new file mode 100644 index 000000000..8e1919bcc --- /dev/null +++ b/include/linear_solvers/solver_base.h @@ -0,0 +1,82 @@ +#ifndef MPM_SOLVER_BASE_H_ +#define MPM_SOLVER_BASE_H_ + +#include "data_types.h" +#include "logger.h" +#include + +namespace mpm { +template +class SolverBase { + public: + //! Constructor with min and max iterations and tolerance + //! \param[in] max_iter Maximum number of iterations + //! \param[in] tolerance Tolerance for solver to achieve convergence + SolverBase(unsigned max_iter, double tolerance) { + max_iter_ = max_iter; + tolerance_ = tolerance; + //! Logger + std::string logger = "SolverBase::"; + console_ = std::make_unique(logger, mpm::stdout_sink); + }; + + //! Destructor + virtual ~SolverBase(){}; + + //! Matrix solver with default initial guess + virtual Eigen::VectorXd solve(const Eigen::SparseMatrix& A, + const Eigen::VectorXd& b) = 0; + + //! Assign global active dof + virtual void assign_global_active_dof(unsigned global_active_dof) = 0; + + //! Assign rank to global mapper + virtual void assign_rank_global_mapper( + std::vector rank_global_mapper) = 0; + + //! Set sub solver type + void set_sub_solver_type(const std::string& type) noexcept { + sub_solver_type_ = type; + } + + //! Set preconditioner type + void set_preconditioner_type(const std::string& type) noexcept { + preconditioner_type_ = type; + } + + //! Set maximum number of iterations + void set_max_iteration(unsigned max_iter) noexcept { max_iter_ = max_iter; } + + //! Set relative iteration tolerance + void set_tolerance(double tol) noexcept { tolerance_ = tol; } + + //! Set absolute iteration tolerance + void set_abs_tolerance(double tol) noexcept { abs_tolerance_ = tol; } + + //! Set divergence iteration tolerance + void set_div_tolerance(double tol) noexcept { div_tolerance_ = tol; } + + //! Set verbosity + void set_verbosity(unsigned v) noexcept { verbosity_ = v; } + + protected: + //! Solver type + std::string sub_solver_type_{"cg"}; + //! Preconditioner type + std::string preconditioner_type_{"none"}; + //! Maximum number of iterations + unsigned max_iter_; + //! Relative tolerance + double tolerance_; + //! Absolute tolerance + double abs_tolerance_; + //! Divergence tolerance + double div_tolerance_; + //! Verbosity + unsigned verbosity_{0}; + //! Logger + std::unique_ptr console_; +}; +} // namespace mpm + +#endif \ No newline at end of file diff --git a/include/loads_bcs/constraints.h b/include/loads_bcs/constraints.h index 0efdef6f6..6e03a6f6d 100644 --- a/include/loads_bcs/constraints.h +++ b/include/loads_bcs/constraints.h @@ -6,6 +6,7 @@ #include "friction_constraint.h" #include "logger.h" #include "mesh.h" +#include "pressure_constraint.h" #include "velocity_constraint.h" namespace mpm { @@ -48,6 +49,21 @@ class Constraints { const std::vector>& friction_constraints); + //! Assign nodal pressure constraints + //! \param[in] mfunction Math function if defined + //! \param[in] setid Node set id + //! \param[in] phase Index corresponding to the phase + //! \param[in] pconstraint Pressure constraint at node + bool assign_nodal_pressure_constraint( + const std::shared_ptr& mfunction, int set_id, + unsigned phase, double pconstraint); + + //! Assign nodal pressure constraints to nodes + //! \param[in] pressure_constraints Constraint at node, pressure + bool assign_nodal_pressure_constraints( + const unsigned phase, + const std::vector>& pressure_constraints); + private: //! Mesh object std::shared_ptr> mesh_; diff --git a/include/loads_bcs/constraints.tcc b/include/loads_bcs/constraints.tcc index 5813e609f..a5b90ed4d 100644 --- a/include/loads_bcs/constraints.tcc +++ b/include/loads_bcs/constraints.tcc @@ -105,3 +105,53 @@ bool mpm::Constraints::assign_nodal_friction_constraints( } return status; } + +//! Assign nodal pressure constraints +template +bool mpm::Constraints::assign_nodal_pressure_constraint( + const std::shared_ptr& mfunction, int set_id, unsigned phase, + double pconstraint) { + bool status = true; + try { + auto nset = mesh_->nodes(set_id); + if (nset.size() == 0) + throw std::runtime_error( + "Node set is empty for assignment of pressure constraints"); + +#pragma omp parallel for schedule(runtime) + for (auto nitr = nset.cbegin(); nitr != nset.cend(); ++nitr) { + if (!(*nitr)->assign_pressure_constraint(phase, pconstraint, mfunction)) + throw std::runtime_error("Setting pressure constraint failed"); + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Assign nodal pressure constraints to nodes +template +bool mpm::Constraints::assign_nodal_pressure_constraints( + const unsigned phase, + const std::vector>& pressure_constraints) { + bool status = true; + try { + for (const auto& pressure_constraint : pressure_constraints) { + // Node id + mpm::Index nid = std::get<0>(pressure_constraint); + // Pressure + double pressure = std::get<1>(pressure_constraint); + + // Apply constraint + if (!mesh_->node(nid)->assign_pressure_constraint(phase, pressure, + nullptr)) + throw std::runtime_error( + "Nodal pressure constraints assignment failed"); + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} diff --git a/include/loads_bcs/pressure_constraint.h b/include/loads_bcs/pressure_constraint.h new file mode 100644 index 000000000..4ea68bb06 --- /dev/null +++ b/include/loads_bcs/pressure_constraint.h @@ -0,0 +1,35 @@ +#ifndef MPM_PRESSURE_CONSTRAINT_H_ +#define MPM_PRESSURE_CONSTRAINT_H_ + +namespace mpm { + +//! PressureConstraint class to store pressure constraint on a set +//! \brief PressureConstraint class to store a constraint on a set +//! \details PressureConstraint stores the constraint as a static value +class PressureConstraint { + public: + // Constructor + //! \param[in] setid set id + //! \param[in] pressure Constraint pressure + PressureConstraint(int setid, unsigned phase, double pressure) + : setid_{setid}, phase_{phase}, pressure_{pressure} {}; + + // Set id + int setid() const { return setid_; } + + // Return phase + unsigned phase() const { return phase_; } + + // Return pressure + double pressure() const { return pressure_; } + + private: + // ID + int setid_; + // Phase + unsigned phase_; + // Velocity + double pressure_; +}; +} // namespace mpm +#endif // MPM_PRESSURE_CONSTRAINT_H_ diff --git a/include/mesh.h b/include/mesh.h index bb51f161a..31863d3ae 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -10,6 +10,7 @@ // Eigen #include "Eigen/Dense" +#include // MPI #ifdef USE_MPI #include "mpi.h" @@ -30,7 +31,6 @@ using Json = nlohmann::json; #include "function_base.h" #include "generators/injection.h" #include "geometry.h" -#include "hdf5_particle.h" #include "io.h" #include "io_mesh.h" #include "logger.h" @@ -39,6 +39,8 @@ using Json = nlohmann::json; #include "node.h" #include "particle.h" #include "particle_base.h" +#include "pod_particle.h" +#include "radial_basis_function.h" #include "traction.h" #include "vector.h" #include "velocity_constraint.h" @@ -100,6 +102,9 @@ class Mesh { //! Return the number of nodes mpm::Index nnodes() const { return nodes_.size(); } + //! Return container of nodes + mpm::Vector> nodes() { return nodes_; } + //! Return the number of nodes in rank mpm::Index nnodes_rank(); @@ -240,6 +245,10 @@ class Mesh { //! Number of particles in the mesh mpm::Index nparticles() const { return particles_.size(); } + //! Number of particles in the mesh with specific type + //! \param[in] particle particle_type A string denoting particle type + mpm::Index nparticles(const std::string& particle_type) const; + //! Locate particles in a cell //! Iterate over all cells in a mesh to find the cell in which particles //! are located. @@ -251,6 +260,12 @@ class Mesh { template void iterate_over_particles(Toper oper); + //! Iterate over particles with predicate + //! \tparam Toper Callable object typically a baseclass functor + //! \tparam Tpred Predicate + template + void iterate_over_particles_predicate(Toper oper, Tpred pred); + //! Iterate over particle set //! \tparam Toper Callable object typically a baseclass functor //! \param[in] set_id particle set id @@ -392,20 +407,41 @@ class Mesh { void find_ghost_boundary_cells(); //! Write HDF5 particles - //! \param[in] phase Index corresponding to the phase //! \param[in] filename Name of HDF5 file to write particles data //! \retval status Status of writing HDF5 output - bool write_particles_hdf5(unsigned phase, const std::string& filename); + bool write_particles_hdf5(const std::string& filename); - //! Read HDF5 particles - //! \param[in] phase Index corresponding to the phase + //! Write HDF5 particles for two-phase-one-point particle + //! \param[in] filename Name of HDF5 file to write particles data + //! \retval status Status of writing HDF5 output + bool write_particles_hdf5_twophase(const std::string& filename); + + //! Read HDF5 particles with type name + //! \param[in] filename Name of HDF5 file to write particles data + //! \param[in] typename Name of particle type name + //! \param[in] particle_type Particle type to be generated + //! \retval status Status of reading HDF5 output + bool read_particles_hdf5(const std::string& filename, + const std::string& type_name, + const std::string& particle_type); + + //! Read HDF5 particles for singlephase particle //! \param[in] filename Name of HDF5 file to write particles data + //! \param[in] particle_type Particle type to be generated //! \retval status Status of reading HDF5 output - bool read_particles_hdf5(unsigned phase, const std::string& filename); + bool read_particles_hdf5(const std::string& filename, + const std::string& particle_type); + + //! Read HDF5 particles for twophase particle + //! \param[in] filename Name of HDF5 file to write particles data + //! \param[in] particle_type Particle type to be generated + //! \retval status Status of reading HDF5 output + bool read_particles_hdf5_twophase(const std::string& filename, + const std::string& particle_type); //! Return HDF5 particles //! \retval particles_hdf5 Vector of HDF5 particles - std::vector particles_hdf5() const; + std::vector particles_hdf5() const; //! Return nodal coordinates std::vector> nodal_coordinates() const; @@ -464,6 +500,101 @@ class Mesh { // Initialise the nodal properties' map void initialise_nodal_properties(); + /** + * \defgroup MultiPhase Functions dealing with multi-phase MPM + */ + /**@{*/ + + //! Compute cell volume fraction + //! \ingroup MultiPhase + //! \details Compute cell volume fraction based on the number of particle + //! see (Kularathna & Soga 2017). + void compute_cell_vol_fraction(); + + //! Compute free surface + //! \ingroup MultiPhase + //! \param[in] method Type of method to use + //! \param[in] volume_tolerance for volume_fraction approach + //! \retval status Status of compute_free_surface + bool compute_free_surface( + const std::string& method = "density", + double volume_tolerance = std::numeric_limits::epsilon()); + + //! Compute free surface by density method + //! \ingroup MultiPhase + //! \details Using simple approach of volume fraction approach as (Kularathna + //! & Soga, 2017) and density ratio comparison (Hamad, 2015). This method is + //! fast, but less accurate. + //! \param[in] volume_tolerance for volume_fraction approach + //! \retval status Status of compute_free_surface + bool compute_free_surface_by_density( + double volume_tolerance = std::numeric_limits::epsilon()); + + //! Compute free surface by geometry method + //! \ingroup MultiPhase + //! \details Using a more expensive approach using neighbouring particles and + //! current geometry. This method combine multiple checks in order to simplify + //! and fasten the process: (1) Volume fraction approach as (Kularathna & Soga + //! 2017), (2) Density comparison approach as (Hamad, 2015), and (3) Geometry + //! based approach as (Marrone et al. 2010) + //! \param[in] volume_tolerance for volume_fraction approach + //! \retval status Status of compute_free_surface + bool compute_free_surface_by_geometry( + double volume_tolerance = std::numeric_limits::epsilon()); + + //! \ingroup MultiPhase + //! \retval id_set Set of free surface node ids + std::set free_surface_nodes(); + + //! Get free surface cell set + //! \ingroup MultiPhase + //! \retval id_set Set of free surface cell ids + std::set free_surface_cells(); + + //! Get free surface particle set + //! \ingroup MultiPhase + //! \retval status Status of compute_free_surface + //! \retval id_set Set of free surface particle ids + std::set free_surface_particles(); + + //! Assign particles pore pressures + //! \ingroup MultiPhase + //! \param[in] particle_pore_pressure Initial pore pressure of particle + bool assign_particles_pore_pressures( + const std::vector>& + particle_pore_pressures); + + //! Create a list of active nodes in mesh and assign active node id + //! (rank-wise) + //! \ingroup MultiPhase + unsigned assign_active_nodes_id(); + + //! Assign active node id (globally in All MPI ranks) + //! \ingroup MultiPhase + unsigned assign_global_active_nodes_id(); + + //! Return container of active nodes + //! \ingroup MultiPhase + mpm::Vector> active_nodes() { return active_nodes_; } + + //! Return global node indices + //! \ingroup MultiPhase + std::vector global_node_indices() const; + + //! Compute correction force in the node + //! \ingroup MultiPhase + bool compute_nodal_correction_force( + const Eigen::SparseMatrix& correction_matrix, + const Eigen::VectorXd& pressure_increment, double dt); + + //! Compute correction force in the node for twophase + //! \ingroup MultiPhase + bool compute_nodal_correction_force_twophase( + const Eigen::SparseMatrix& correction_matrix, + const Eigen::VectorXd& pressure_increment, double dt); + + /**@}*/ + private: // Read particles from file //! \param[in] pset_id Set ID of the particles @@ -536,5 +667,6 @@ class Mesh { } // namespace mpm #include "mesh.tcc" +#include "mesh_multiphase.tcc" #endif // MPM_MESH_H_ diff --git a/include/mesh.tcc b/include/mesh.tcc index 76e8c161e..cca4058ed 100644 --- a/include/mesh.tcc +++ b/include/mesh.tcc @@ -420,6 +420,17 @@ void mpm::Mesh::find_ghost_boundary_cells() { #endif } +//! Number of particles in the mesh with specific type +template +mpm::Index mpm::Mesh::nparticles(const std::string& particle_type) const { + return std::count_if( + particles_.cbegin(), particles_.cend(), + [&ptype = + particle_type](const std::shared_ptr>& ptr) { + return (ptr)->type() == ptype; + }); +} + //! Find ncells in rank template mpm::Index mpm::Mesh::ncells_rank(bool active_cells) { @@ -956,7 +967,6 @@ void mpm::Mesh::resume_domain_cell_ranks() { int mpi_rank = 0; #ifdef USE_MPI MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); - const unsigned rank_max = std::numeric_limits::max(); const unsigned ncells = this->ncells(); // Vector of cell ranks std::vector cell_ranks; @@ -1111,6 +1121,16 @@ void mpm::Mesh::iterate_over_particles(Toper oper) { oper(*pitr); } +//! Iterate over particles +template +template +void mpm::Mesh::iterate_over_particles_predicate(Toper oper, Tpred pred) { +#pragma omp parallel for schedule(runtime) + for (auto pitr = particles_.cbegin(); pitr != particles_.cend(); ++pitr) { + if (pred(*pitr)) oper(*pitr); + } +} + //! Iterate over particle set template template @@ -1470,20 +1490,58 @@ std::vector> mpm::Mesh::particles_cells() //! Write particles to HDF5 template -bool mpm::Mesh::write_particles_hdf5(unsigned phase, - const std::string& filename) { +bool mpm::Mesh::write_particles_hdf5(const std::string& filename) { const unsigned nparticles = this->nparticles(); - std::vector particle_data; // = new HDF5Particle[nparticles]; + std::vector particle_data; particle_data.reserve(nparticles); - for (auto pitr = particles_.cbegin(); pitr != particles_.cend(); ++pitr) - particle_data.emplace_back((*pitr)->hdf5()); + for (auto pitr = particles_.cbegin(); pitr != particles_.cend(); ++pitr) { + auto pod = std::static_pointer_cast((*pitr)->pod()); + particle_data.emplace_back(*pod); + } // Calculate the size and the offsets of our struct members in memory const hsize_t NRECORDS = nparticles; + const hsize_t NFIELDS = mpm::pod::particle::NFIELDS; + + hid_t file_id; + hsize_t chunk_size = 10000; + int* fill_data = NULL; + int compress = 0; + + // Create a new file using default properties. + file_id = + H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); + + // make a table + H5TBmake_table("Table Title", file_id, "table", NFIELDS, NRECORDS, + mpm::pod::particle::dst_size, mpm::pod::particle::field_names, + mpm::pod::particle::dst_offset, mpm::pod::particle::field_type, + chunk_size, fill_data, compress, particle_data.data()); - const hsize_t NFIELDS = mpm::hdf5::particle::NFIELDS; + H5Fclose(file_id); + return true; +} + +//! Write particles to HDF5 for two-phase particle +template +bool mpm::Mesh::write_particles_hdf5_twophase( + const std::string& filename) { + const unsigned nparticles = this->nparticles(); + + std::vector particle_data; + particle_data.reserve(nparticles); + + for (auto pitr = particles_.cbegin(); pitr != particles_.cend(); ++pitr) { + auto pod = + std::static_pointer_cast((*pitr)->pod()); + particle_data.emplace_back(*pod); + } + + // Calculate the size and the offsets of our struct members in memory + const hsize_t NRECORDS = nparticles; + const hsize_t NFIELDS = mpm::pod::particletwophase::NFIELDS; hid_t file_id; hsize_t chunk_size = 10000; @@ -1495,20 +1553,34 @@ bool mpm::Mesh::write_particles_hdf5(unsigned phase, H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); // make a table - H5TBmake_table( - "Table Title", file_id, "table", NFIELDS, NRECORDS, - mpm::hdf5::particle::dst_size, mpm::hdf5::particle::field_names, - mpm::hdf5::particle::dst_offset, mpm::hdf5::particle::field_type, - chunk_size, fill_data, compress, particle_data.data()); + H5TBmake_table("Table Title", file_id, "table", NFIELDS, NRECORDS, + mpm::pod::particletwophase::dst_size, + mpm::pod::particletwophase::field_names, + mpm::pod::particletwophase::dst_offset, + mpm::pod::particletwophase::field_type, chunk_size, fill_data, + compress, particle_data.data()); H5Fclose(file_id); return true; } -//! Write particles to HDF5 +//! Read HDF5 particles with type name +template +bool mpm::Mesh::read_particles_hdf5(const std::string& filename, + const std::string& type_name, + const std::string& particle_type) { + bool status = false; + if (type_name == "particles" || type_name == "fluid_particles") + status = this->read_particles_hdf5(filename, particle_type); + else if (type_name == "twophase_particles") + status = this->read_particles_hdf5_twophase(filename, particle_type); + return status; +} + +//! Read HDF5 particles for singlephase particle template -bool mpm::Mesh::read_particles_hdf5(unsigned phase, - const std::string& filename) { +bool mpm::Mesh::read_particles_hdf5(const std::string& filename, + const std::string& particle_type) { // Create a new file using default properties. hid_t file_id = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); @@ -1520,24 +1592,83 @@ bool mpm::Mesh::read_particles_hdf5(unsigned phase, hsize_t nfields = 0; H5TBget_table_info(file_id, "table", &nfields, &nrecords); - if (nfields != mpm::hdf5::particle::NFIELDS) + if (nfields != mpm::pod::particle::NFIELDS) throw std::runtime_error("HDF5 table has incorrect number of fields"); - std::vector dst_buf; + std::vector dst_buf; dst_buf.reserve(nrecords); // Read the table - H5TBread_table(file_id, "table", mpm::hdf5::particle::dst_size, - mpm::hdf5::particle::dst_offset, - mpm::hdf5::particle::dst_sizes, dst_buf.data()); + H5TBread_table(file_id, "table", mpm::pod::particle::dst_size, + mpm::pod::particle::dst_offset, mpm::pod::particle::dst_sizes, + dst_buf.data()); - // Particle type - const std::string particle_type = (Tdim == 2) ? "P2D" : "P3D"; + // Iterate over all HDF5 particles + for (unsigned i = 0; i < nrecords; ++i) { + PODParticle pod_particle = dst_buf[i]; + // Get particle's material from list of materials + std::vector>> materials; + materials.emplace_back(materials_.at(pod_particle.material_id)); + + // Particle id + mpm::Index pid = pod_particle.id; + // Initialise coordinates + Eigen::Matrix coords; + coords.setZero(); + + // Create particle + auto particle = + Factory, mpm::Index, + const Eigen::Matrix&>::instance() + ->create(particle_type, static_cast(pid), coords); + + // Initialise particle with HDF5 data + particle->initialise_particle(pod_particle, materials); + + // Add particle to mesh and check + bool insert_status = this->add_particle(particle, false); + + // If insertion is successful + if (!insert_status) + throw std::runtime_error("Addition of particle to mesh failed!"); + } + // close the file + H5Fclose(file_id); + return true; +} + +//! Read HDF5 particles for twophase particle +template +bool mpm::Mesh::read_particles_hdf5_twophase( + const std::string& filename, const std::string& particle_type) { + + // Create a new file using default properties. + hid_t file_id = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); + // Throw an error if file can't be found + if (file_id < 0) throw std::runtime_error("HDF5 particle file is not found"); + + // Calculate the size and the offsets of our struct members in memory + hsize_t nrecords = 0; + hsize_t nfields = 0; + H5TBget_table_info(file_id, "table", &nfields, &nrecords); + + if (nfields != mpm::pod::particletwophase::NFIELDS) + throw std::runtime_error("HDF5 table has incorrect number of fields"); + + std::vector dst_buf; + dst_buf.reserve(nrecords); + // Read the table + H5TBread_table(file_id, "table", mpm::pod::particletwophase::dst_size, + mpm::pod::particletwophase::dst_offset, + mpm::pod::particletwophase::dst_sizes, dst_buf.data()); // Iterate over all HDF5 particles for (unsigned i = 0; i < nrecords; ++i) { - HDF5Particle pod_particle = dst_buf[i]; + PODParticleTwoPhase pod_particle = dst_buf[i]; // Get particle's material from list of materials - auto material = materials_.at(pod_particle.material_id); + // Get particle's material from list of materials + std::vector>> materials; + materials.emplace_back(materials_.at(pod_particle.material_id)); + materials.emplace_back(materials_.at(pod_particle.liquid_material_id)); // Particle id mpm::Index pid = pod_particle.id; // Initialise coordinates @@ -1551,7 +1682,7 @@ bool mpm::Mesh::read_particles_hdf5(unsigned phase, ->create(particle_type, static_cast(pid), coords); // Initialise particle with HDF5 data - particle->initialise_particle(pod_particle, material); + particle->initialise_particle(pod_particle, materials); // Add particle to mesh and check bool insert_status = this->add_particle(particle, false); @@ -1567,14 +1698,16 @@ bool mpm::Mesh::read_particles_hdf5(unsigned phase, //! Write particles to HDF5 template -std::vector mpm::Mesh::particles_hdf5() const { +std::vector mpm::Mesh::particles_hdf5() const { const unsigned nparticles = this->nparticles(); - std::vector particles_hdf5; + std::vector particles_hdf5; particles_hdf5.reserve(nparticles); - for (auto pitr = particles_.cbegin(); pitr != particles_.cend(); ++pitr) - particles_hdf5.emplace_back((*pitr)->hdf5()); + for (auto pitr = particles_.cbegin(); pitr != particles_.cend(); ++pitr) { + auto pod = std::static_pointer_cast((*pitr)->pod()); + particles_hdf5.emplace_back(*pod); + } return particles_hdf5; } @@ -1972,7 +2105,8 @@ void mpm::Mesh::create_nodal_properties() { // Initialise the shared pointer to nodal properties nodal_properties_ = std::make_shared(); - // Check if nodes_ and materials_is empty and throw runtime error if they are + // Check if nodes_ and materials_is empty and throw runtime error if they + // are if (nodes_.size() != 0 && materials_.size() != 0) { // Compute number of rows in nodal properties for vector entities const unsigned nrows = nodes_.size() * Tdim; @@ -2006,4 +2140,4 @@ template void mpm::Mesh::initialise_nodal_properties() { // Call initialise_properties function from the nodal properties nodal_properties_->initialise_nodal_properties(); -} +} \ No newline at end of file diff --git a/include/mesh_multiphase.tcc b/include/mesh_multiphase.tcc new file mode 100644 index 000000000..7ae0e17e0 --- /dev/null +++ b/include/mesh_multiphase.tcc @@ -0,0 +1,677 @@ +//! Compute free surface cells, nodes, and particles +template +bool mpm::Mesh::compute_free_surface(const std::string& method, + double volume_tolerance) { + if (method == "density") { + return this->compute_free_surface_by_density(volume_tolerance); + } else if (method == "geometry") { + return this->compute_free_surface_by_geometry(volume_tolerance); + } else { + console_->info( + "The selected free-surface computation method: {}\n is not " + "available. " + "Using density approach as default method.", + method); + return this->compute_free_surface_by_density(volume_tolerance); + } +} + +//! Compute free surface cells, nodes, and particles by density and geometry +template +bool mpm::Mesh::compute_free_surface_by_geometry( + double volume_tolerance) { + bool status = true; + + // Reset free surface cell and particles + this->iterate_over_cells(std::bind(&mpm::Cell::assign_free_surface, + std::placeholders::_1, false)); + + VectorDim temp_normal; + temp_normal.setZero(); + this->iterate_over_particles_predicate( + std::bind(&mpm::ParticleBase::assign_normal, std::placeholders::_1, + temp_normal), + std::bind(&mpm::ParticleBase::free_surface, std::placeholders::_1)); + + this->iterate_over_particles( + std::bind(&mpm::ParticleBase::assign_free_surface, + std::placeholders::_1, false)); + + // Reset volume fraction + this->iterate_over_cells(std::bind(&mpm::Cell::assign_volume_fraction, + std::placeholders::_1, 0.0)); + + // Compute and assign volume fraction to each cell + this->compute_cell_vol_fraction(); + + // First, we detect the cell with possible free surfaces + // Compute boundary cells and nodes based on geometry + std::set free_surface_candidate_cells; +#pragma omp parallel for schedule(runtime) + for (auto citr = this->cells_.cbegin(); citr != this->cells_.cend(); ++citr) { + // Cell contains particles + if ((*citr)->status()) { + bool candidate_cell = false; + const auto& node_id = (*citr)->nodes_id(); + if ((*citr)->volume_fraction() < volume_tolerance) { + candidate_cell = true; + for (const auto id : node_id) { + map_nodes_[id]->assign_free_surface(true); + } + } else { + // Loop over neighbouring cells + for (const auto neighbour_cell_id : (*citr)->neighbours()) { + if (!map_cells_[neighbour_cell_id]->status()) { + candidate_cell = true; + const auto& n_node_id = map_cells_[neighbour_cell_id]->nodes_id(); + + // Detect common node id + std::set common_node_id; + std::set_intersection( + node_id.begin(), node_id.end(), n_node_id.begin(), + n_node_id.end(), + std::inserter(common_node_id, common_node_id.begin())); + + // Assign free surface nodes + if (!common_node_id.empty()) { + for (const auto common_id : common_node_id) { + map_nodes_[common_id]->assign_free_surface(true); + } + } + } + } + } + + // Assign free surface cell + if (candidate_cell) { + (*citr)->assign_free_surface(true); +#pragma omp critical + free_surface_candidate_cells.insert((*citr)->id()); + } + } + } + + // Compute particle neighbours for particles at candidate cells + std::vector free_surface_candidate_particles_first; + for (const auto cell_id : free_surface_candidate_cells) { + this->find_particle_neighbours(map_cells_[cell_id]); + const auto& particle_ids = map_cells_[cell_id]->particles(); + free_surface_candidate_particles_first.insert( + free_surface_candidate_particles_first.end(), particle_ids.begin(), + particle_ids.end()); + } + + // Compute boundary particles based on density function + // Lump cell volume to nodes + this->iterate_over_cells(std::bind(&mpm::Cell::map_cell_volume_to_nodes, + std::placeholders::_1, 0)); + + // Compute nodal value of mass density + this->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::compute_density, std::placeholders::_1), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + std::set free_surface_candidate_particles_second; + for (const auto p_id : free_surface_candidate_particles_first) { + const auto& particle = map_particles_[p_id]; + status = particle->compute_free_surface_by_density(); + if (status) free_surface_candidate_particles_second.insert(p_id); + } + + // Find free surface particles through geometry + for (const auto p_id : free_surface_candidate_particles_second) { + // Initialize renormalization matrix + Eigen::Matrix renormalization_matrix_inv; + renormalization_matrix_inv.setZero(); + + // Loop over neighbours + const auto& particle = map_particles_[p_id]; + const auto& p_coord = particle->coordinates(); + const auto& neighbour_particles = particle->neighbours(); + const double smoothing_length = 1.33 * particle->diameter(); + for (const auto neighbour_particle_id : neighbour_particles) { + const auto& n_coord = + map_particles_[neighbour_particle_id]->coordinates(); + const VectorDim rel_coord = n_coord - p_coord; + + // Compute kernel gradient + const VectorDim kernel_gradient = + mpm::RadialBasisFunction::gradient(smoothing_length, -rel_coord, + "gaussian"); + + // Inverse of renormalization matrix B + renormalization_matrix_inv += + (particle->mass() / particle->mass_density()) * kernel_gradient * + rel_coord.transpose(); + } + + // Compute lambda: minimum eigenvalue of B_inverse + Eigen::SelfAdjointEigenSolver es( + renormalization_matrix_inv); + double lambda = es.eigenvalues().minCoeff(); + + // Categorize particle based on lambda + bool free_surface = false; + bool secondary_check = false; + bool interior = false; + if (lambda <= 0.2) + free_surface = true; + else if (lambda > 0.2 && lambda <= 0.75) + secondary_check = true; + else + interior = true; + + // Compute numerical normal vector + VectorDim normal; + normal.setZero(); + if (!interior) { + VectorDim temporary_vec; + temporary_vec.setZero(); + for (const auto neighbour_particle_id : neighbour_particles) { + const auto& n_coord = + map_particles_[neighbour_particle_id]->coordinates(); + const VectorDim rel_coord = n_coord - p_coord; + + // Compute kernel gradient + const VectorDim kernel_gradient = + mpm::RadialBasisFunction::gradient(smoothing_length, + -rel_coord, "gaussian"); + + // Sum of kernel by volume + temporary_vec += + (particle->mass() / particle->mass_density()) * kernel_gradient; + } + normal = -renormalization_matrix_inv.inverse() * temporary_vec; + if (normal.norm() > std::numeric_limits::epsilon()) + normal.normalize(); + else + normal.setZero(); + } + + // If secondary check is needed + if (secondary_check) { + // Construct scanning region + // TODO: spacing distance should be a function of porosity + const double spacing_distance = smoothing_length; + VectorDim t_coord = p_coord + spacing_distance * normal; + + // Check all neighbours + for (const auto neighbour_particle_id : neighbour_particles) { + const auto& n_coord = + map_particles_[neighbour_particle_id]->coordinates(); + const VectorDim rel_coord_np = n_coord - p_coord; + const double distance_np = rel_coord_np.norm(); + const VectorDim rel_coord_nt = n_coord - t_coord; + const double distance_nt = rel_coord_nt.norm(); + + free_surface = true; + if (distance_np < std::sqrt(2) * spacing_distance) { + if (std::acos(normal.dot(rel_coord_np) / distance_np) < M_PI / 4) { + free_surface = false; + break; + } + } else { + if (distance_nt < spacing_distance) { + free_surface = false; + break; + } + } + } + } + + // Assign normal only to validated free surface + if (free_surface) { + particle->assign_free_surface(true); + particle->assign_normal(normal); + } + } + + return status; +} + +//! Compute free surface cells, nodes, and particles by density +template +bool mpm::Mesh::compute_free_surface_by_density(double volume_tolerance) { + bool status = true; + + // Get number of MPI ranks + int mpi_rank = 0; + int mpi_size = 1; + +#ifdef USE_MPI + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); + MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); + + // Initialize pointer of booleans for send and receive + bool* send_cell_solving_status = new bool[ncells()]; + memset(send_cell_solving_status, 0, ncells() * sizeof(bool)); + bool* receive_cell_solving_status = new bool[ncells()]; + +#pragma omp parallel for schedule(runtime) + for (auto citr = cells_.cbegin(); citr != cells_.cend(); ++citr) + if ((*citr)->status()) + // Assign solving status for MPI solver + send_cell_solving_status[(*citr)->id()] = true; + else + send_cell_solving_status[(*citr)->id()] = false; + + MPI_Allreduce(send_cell_solving_status, receive_cell_solving_status, ncells(), + MPI_CXX_BOOL, MPI_LOR, MPI_COMM_WORLD); + +#pragma omp parallel for schedule(runtime) + for (auto citr = cells_.cbegin(); citr != cells_.cend(); ++citr) { + // Assign solving status for MPI solver + (*citr)->assign_solving_status(receive_cell_solving_status[(*citr)->id()]); + } + + delete[] send_cell_solving_status; + delete[] receive_cell_solving_status; +#endif + + // Reset free surface cell + this->iterate_over_cells(std::bind(&mpm::Cell::assign_free_surface, + std::placeholders::_1, false)); + + // Reset volume fraction + this->iterate_over_cells(std::bind(&mpm::Cell::assign_volume_fraction, + std::placeholders::_1, 0.0)); + + // Reset free surface particle + this->iterate_over_particles( + std::bind(&mpm::ParticleBase::assign_free_surface, + std::placeholders::_1, false)); + + // Compute and assign volume fraction to each cell + this->compute_cell_vol_fraction(); + +#ifdef USE_MPI + // Initialize vector of double for send and receive + std::vector send_cell_vol_fraction; + send_cell_vol_fraction.resize(ncells()); + std::vector receive_cell_vol_fraction; + receive_cell_vol_fraction.resize(ncells()); + +#pragma omp parallel for schedule(runtime) + for (auto citr = cells_.cbegin(); citr != cells_.cend(); ++citr) + if ((*citr)->status()) + // Assign volume_fraction for MPI solver + send_cell_vol_fraction[(*citr)->id()] = (*citr)->volume_fraction(); + else + send_cell_vol_fraction[(*citr)->id()] = 0.0; + + MPI_Allreduce(send_cell_vol_fraction.data(), receive_cell_vol_fraction.data(), + ncells(), MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); + +#pragma omp parallel for schedule(runtime) + for (auto citr = cells_.cbegin(); citr != cells_.cend(); ++citr) { + // Assign volume_fraction for MPI solver + (*citr)->assign_volume_fraction(receive_cell_vol_fraction[(*citr)->id()]); + } +#endif + +#pragma omp parallel for schedule(runtime) + // Compute boundary cells and nodes based on geometry + for (auto citr = this->cells_.cbegin(); citr != this->cells_.cend(); ++citr) { + + if ((*citr)->status()) { + bool cell_at_interface = false; + const auto& node_id = (*citr)->nodes_id(); + bool internal = true; + + //! Check internal cell + for (const auto neighbour_cell_id : (*citr)->neighbours()) { +#if USE_MPI + if (!map_cells_[neighbour_cell_id]->solving_status()) { + internal = false; + break; + } +#else + if (!map_cells_[neighbour_cell_id]->status()) { + internal = false; + break; + } +#endif + } + + //! Check volume fraction only for boundary cell + if (!internal) { + if ((*citr)->volume_fraction() < volume_tolerance) { + cell_at_interface = true; + for (const auto id : node_id) { + map_nodes_[id]->assign_free_surface(cell_at_interface); + } + } else { + for (const auto neighbour_cell_id : (*citr)->neighbours()) { + if (map_cells_[neighbour_cell_id]->volume_fraction() < + volume_tolerance) { + cell_at_interface = true; + const auto& n_node_id = map_cells_[neighbour_cell_id]->nodes_id(); + + // Detect common node id + std::set common_node_id; + std::set_intersection( + node_id.begin(), node_id.end(), n_node_id.begin(), + n_node_id.end(), + std::inserter(common_node_id, common_node_id.begin())); + + // Assign free surface nodes + if (!common_node_id.empty()) { + for (const auto common_id : common_node_id) { + map_nodes_[common_id]->assign_free_surface(true); + } + } + } + } + } + + // Assign free surface cell + if (cell_at_interface) { + (*citr)->assign_free_surface(cell_at_interface); + } + } + } + } + + // Compute boundary particles based on density function + // Lump cell volume to nodes + this->iterate_over_cells(std::bind(&mpm::Cell::map_cell_volume_to_nodes, + std::placeholders::_1, + mpm::ParticlePhase::SinglePhase)); + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce nodal volume + this->template nodal_halo_exchange( + std::bind(&mpm::NodeBase::volume, std::placeholders::_1, + mpm::ParticlePhase::SinglePhase), + std::bind(&mpm::NodeBase::update_volume, std::placeholders::_1, + false, mpm::ParticlePhase::SinglePhase, + std::placeholders::_2)); + } +#endif + + // Compute nodal value of mass density + this->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::compute_density, std::placeholders::_1), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Evaluate free surface particles + this->iterate_over_particles( + [](std::shared_ptr> ptr) { + bool status = ptr->compute_free_surface_by_density(); + if (status) { + return ptr->assign_free_surface(status); + } + }); + + return status; +} + +//! Get free surface node set +template +std::set mpm::Mesh::free_surface_nodes() { + std::set id_set; + for (auto nitr = this->nodes_.cbegin(); nitr != this->nodes_.cend(); ++nitr) + if ((*nitr)->free_surface()) id_set.insert((*nitr)->id()); + return id_set; +} + +//! Get free surface cell set +template +std::set mpm::Mesh::free_surface_cells() { + std::set id_set; + for (auto citr = this->cells_.cbegin(); citr != this->cells_.cend(); ++citr) + if ((*citr)->free_surface()) id_set.insert((*citr)->id()); + return id_set; +} + +//! Get free surface particle set +template +std::set mpm::Mesh::free_surface_particles() { + std::set id_set; + for (auto pitr = this->particles_.cbegin(); pitr != this->particles_.cend(); + ++pitr) + if ((*pitr)->free_surface()) id_set.insert((*pitr)->id()); + return id_set; +} + +//! Compute cell volume fraction +template +void mpm::Mesh::compute_cell_vol_fraction() { + this->iterate_over_cells([&map_particles = map_particles_]( + std::shared_ptr> c_ptr) { + if (c_ptr->status()) { + // Compute volume fraction + double cell_volume_fraction = 0.0; + for (const auto p_id : c_ptr->particles()) + cell_volume_fraction += map_particles[p_id]->volume(); + + cell_volume_fraction = cell_volume_fraction / c_ptr->volume(); + return c_ptr->assign_volume_fraction(cell_volume_fraction); + } + }); +} + +//! Assign particle pore pressures +template +bool mpm::Mesh::assign_particles_pore_pressures( + const std::vector>& + particle_pore_pressures) { + bool status = true; + + try { + if (!particles_.size()) + throw std::runtime_error( + "No particles have been assigned in mesh, cannot assign pore " + "pressures"); + + for (const auto& particle_pore_pressure : particle_pore_pressures) { + // Particle id + mpm::Index pid = std::get<0>(particle_pore_pressure); + // Pore pressure + double pore_pressure = std::get<1>(particle_pore_pressure); + + if (map_particles_.find(pid) != map_particles_.end()) + map_particles_[pid]->assign_pressure(pore_pressure, + mpm::ParticlePhase::Liquid); + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Create a list of active nodes in mesh and assign active node id +//! (rank-wise) +template +unsigned mpm::Mesh::assign_active_nodes_id() { + // Clear existing list of active nodes + this->active_nodes_.clear(); + Index active_id = 0; + +#ifdef USE_MPI + // Initialize pointer of booleans for send and receive + bool* send_nodal_solving_status = new bool[nnodes()]; + memset(send_nodal_solving_status, 0, nnodes() * sizeof(bool)); + bool* receive_nodal_solving_status = new bool[nnodes()]; + + for (auto nitr = nodes_.cbegin(); nitr != nodes_.cend(); ++nitr) { + if ((*nitr)->status()) { + this->active_nodes_.add(*nitr); + (*nitr)->assign_active_id(active_id); + active_id++; + + // Assign solving status for MPI solver + send_nodal_solving_status[(*nitr)->id()] = true; + + } else { + (*nitr)->assign_active_id(std::numeric_limits::max()); + } + } + + MPI_Allreduce(send_nodal_solving_status, receive_nodal_solving_status, + nnodes(), MPI_CXX_BOOL, MPI_LOR, MPI_COMM_WORLD); + +#pragma omp parallel for schedule(runtime) + for (auto nitr = nodes_.cbegin(); nitr != nodes_.cend(); ++nitr) { + if (receive_nodal_solving_status[(*nitr)->id()]) { + // Assign solving status for MPI solver + (*nitr)->assign_solving_status(true); + } + } + + delete[] send_nodal_solving_status; + delete[] receive_nodal_solving_status; + +#else + for (auto nitr = nodes_.cbegin(); nitr != nodes_.cend(); ++nitr) { + if ((*nitr)->status()) { + this->active_nodes_.add(*nitr); + (*nitr)->assign_active_id(active_id); + active_id++; + + } else { + (*nitr)->assign_active_id(std::numeric_limits::max()); + } + } +#endif + + return active_id; +} + +//! Assign active node id (globally in All MPI ranks) +template +unsigned mpm::Mesh::assign_global_active_nodes_id() { + // Clear existing list of active nodes + Index global_active_id = 0; + +#ifdef USE_MPI + for (auto nitr = nodes_.cbegin(); nitr != nodes_.cend(); ++nitr) { + if ((*nitr)->solving_status()) { + (*nitr)->assign_global_active_id(global_active_id); + global_active_id++; + } else { + (*nitr)->assign_global_active_id(std::numeric_limits::max()); + } + } +#endif + + return global_active_id; +} + +//! Return global node indices +template +std::vector mpm::Mesh::global_node_indices() const { + // Vector of node_pairs + std::vector node_indices; + try { + // Iterate over cells + for (auto citr = cells_.cbegin(); citr != cells_.cend(); ++citr) { + if ((*citr)->status()) { + node_indices.emplace_back((*citr)->local_node_indices()); + } + } + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return node_indices; +} + +//! Compute nodal correction force +template +bool mpm::Mesh::compute_nodal_correction_force( + const Eigen::SparseMatrix& correction_matrix, + const Eigen::VectorXd& pressure_increment, double dt) { + bool status = true; + try { + //! Active node size + const auto nactive_node = active_nodes_.size(); + + // Part of nodal correction force of one direction + Eigen::MatrixXd correction_force; + correction_force.resize(nactive_node, Tdim); + + // Iterate over each direction + for (unsigned i = 0; i < Tdim; ++i) { + correction_force.block(0, i, nactive_node, 1) = + -correction_matrix.block(0, nactive_node * i, nactive_node, + nactive_node) * + pressure_increment; + } + +#pragma omp parallel for schedule(runtime) + // Iterate over each active node + for (auto nitr = active_nodes_.cbegin(); nitr != active_nodes_.cend(); + ++nitr) { + unsigned active_id = (*nitr)->active_id(); + VectorDim nodal_correction_force = + (correction_force.row(active_id)).transpose(); + + // Compute correction force for each node + map_nodes_[(*nitr)->id()]->update_correction_force( + false, mpm::NodePhase::NSinglePhase, nodal_correction_force); + } + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return status; +}; + +//! Compute nodal corrected force for twophase +template +bool mpm::Mesh::compute_nodal_correction_force_twophase( + const Eigen::SparseMatrix& correction_matrix, + const Eigen::VectorXd& pressure_increment, double dt) { + bool status = true; + try { + //! Active node size + const auto nactive_node = active_nodes_.size(); + + // Part of nodal corrected force of one direction + Eigen::MatrixXd correction_force; + correction_force.resize(nactive_node * 2, Tdim); + + // Iterate over each direction + for (unsigned i = 0; i < Tdim; ++i) { + // Solid phase + correction_force.block(0, i, nactive_node, 1) = + -correction_matrix.block(0, nactive_node * i, nactive_node, + nactive_node) * + pressure_increment; + // Liquid phase + correction_force.block(nactive_node, i, nactive_node, 1) = + -correction_matrix.block(nactive_node, nactive_node * i, nactive_node, + nactive_node) * + pressure_increment; + } + + // Iterate over each active node +#pragma omp parallel for schedule(runtime) + // Iterate over each active node + for (auto nitr = active_nodes_.cbegin(); nitr != active_nodes_.cend(); + ++nitr) { + //! Active id + unsigned active_id = (*nitr)->active_id(); + // Solid phase + VectorDim nodal_correction_force_solid = + (correction_force.row(active_id)).transpose(); + // Liquid phase + VectorDim nodal_correction_force_liquid = + (correction_force.row(active_id + nactive_node)).transpose(); + + // Compute corrected force for each node + map_nodes_[(*nitr)->id()]->update_correction_force( + false, mpm::NodePhase::NSolid, nodal_correction_force_solid); + map_nodes_[(*nitr)->id()]->update_correction_force( + false, mpm::NodePhase::NLiquid, nodal_correction_force_liquid); + } + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return status; +}; \ No newline at end of file diff --git a/include/node.h b/include/node.h index 31a6b9a03..92dcd3277 100644 --- a/include/node.h +++ b/include/node.h @@ -66,6 +66,12 @@ class Node : public NodeBase { //! Return status bool status() const override { return status_; } + //! Assign solving status + void assign_solving_status(bool status) override { solving_status_ = status; } + + //! Return solving status + bool solving_status() const override { return solving_status_; } + //! Update mass at the nodes from particle //! \param[in] update A boolean to update (true) or assign (false) //! \param[in] phase Index corresponding to the phase @@ -134,6 +140,21 @@ class Node : public NodeBase { void update_mass_pressure(unsigned phase, double mass_pressure) noexcept override; + //! Assign pressure constraint + //! \param[in] phase Index corresponding to the phase + //! \param[in] pressure Applied pressure constraint + //! \param[in] function math function + bool assign_pressure_constraint( + unsigned phase, double pressure, + const std::shared_ptr& function) override; + + //! Apply pressure constraint + //! \param[in] phase Index corresponding to the phase + //! \param[in] dt Timestep in analysis + //! \param[in] step Step in analysis + void apply_pressure_constraint(unsigned phase, double dt = 0, + Index step = 0) noexcept override; + //! Assign pressure at the nodes from particle //! \param[in] update A boolean to update (true) or assign (false) //! \param[in] phase Index corresponding to the phase @@ -264,6 +285,160 @@ class Node : public NodeBase { //! Compute multimaterial normal unit vector void compute_multimaterial_normal_unit_vector() override; + /** + * \defgroup MultiPhase Functions dealing with multi-phase MPM + */ + /**@{*/ + + //! Return interpolated density at a given node for a given phase + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + double density(unsigned phase) const override { return density_(phase); } + + //! Compute nodal density + //! \ingroup MultiPhase + void compute_density() override; + + //! Assign free surface + //! \ingroup MultiPhase + void assign_free_surface(bool free_surface) override { + node_mutex_.lock(); + free_surface_ = free_surface; + node_mutex_.unlock(); + } + + //! Return free surface bool + //! \ingroup MultiPhase + bool free_surface() const override { return free_surface_; } + + //! Initialise two-phase nodal properties + //! \ingroup MultiPhase + void initialise_twophase() noexcept override; + + //! Update internal force (body force / traction force) + //! \ingroup MultiPhase + //! \param[in] update A boolean to update (true) or assign (false) + //! \param[in] drag_force Drag force from the particles in a cell + //! \retval status Update status + void update_drag_force_coefficient(bool update, + const VectorDim& drag_force) override; + + //! Return drag force at a given node + //! \ingroup MultiPhase + VectorDim drag_force_coefficient() const override { + return drag_force_coefficient_; + } + + //! Compute acceleration and velocity for two phase + //! \ingroup MultiPhase + //! \param[in] dt Timestep in analysis + bool compute_acceleration_velocity_twophase_explicit( + double dt) noexcept override; + + //! Compute acceleration and velocity for two phase with cundall damping + //! \ingroup MultiPhase + //! \param[in] dt Timestep in analysis \param[in] damping_factor + //! Damping factor + bool compute_acceleration_velocity_twophase_explicit_cundall( + double dt, double damping_factor) noexcept override; + + //! Compute semi-implicit acceleration and velocity + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + //! \param[in] dt Timestep in analysis + //! \details Can be used for both semi-implicit navier-stokes and two-phase + //! solvers + //! \retval status Computation status + bool compute_acceleration_velocity_semi_implicit_corrector( + unsigned phase, double dt) override; + + //! Compute semi-implicit acceleration and velocity with Cundall damping + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + //! \param[in] dt Timestep in analysis + //! \details Can be used for both semi-implicit navier-stokes and two-phase + //! solvers + //! \retval status Computation status + bool compute_acceleration_velocity_semi_implicit_corrector_cundall( + unsigned phase, double dt, double damping_factor); + + //! Assign active id + //! \ingroup MultiPhase + void assign_active_id(Index id) override { active_id_ = id; } + + //! Return active id + //! \ingroup MultiPhase + mpm::Index active_id() const override { return active_id_; } + + //! Assign global active id + //! \ingroup MultiPhase + void assign_global_active_id(Index id) override { global_active_id_ = id; } + + //! Return global active id + //! \ingroup MultiPhase + mpm::Index global_active_id() const override { return global_active_id_; } + + //! Return nodal pressure constraint + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + //! \param[in] current_time current time of the analysis + //! \retval pressure constraint at proper time for given phase + double pressure_constraint(const unsigned phase, + const double current_time) const override { + double constraint = std::numeric_limits::max(); + if (pressure_constraints_.find(phase) != pressure_constraints_.end()) { + const double scalar = + (pressure_function_.find(phase) != pressure_function_.end()) + ? pressure_function_.at(phase)->value(current_time) + : 1.0; + + constraint = scalar * pressure_constraints_.at(phase); + } + return constraint; + } + + //! Update pressure increment at the node + //! \ingroup MultiPhase + void update_pressure_increment(const Eigen::VectorXd& pressure_increment, + unsigned phase, + double current_time = 0.) override; + + //! Return nodal pressure increment + //! \ingroup MultiPhase + double pressure_increment() const override { return pressure_increment_; } + + //! Return map of velocity constraints + //! \ingroup MultiPhase + std::map& velocity_constraints() override { + return velocity_constraints_; + } + + //! Update nodal intermediate velocity + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + //! \param[in] acceleration_inter solved intermediate acceleration + //! \param[in] dt Timestep in analysis + void update_intermediate_acceleration_velocity( + const unsigned phase, const Eigen::MatrixXd& acceleration_inter, + double dt) override; + + //! Update correction force + //! \ingroup MultiPhase + //! \param[in] update A boolean to update (true) or assign (false) + //! \param[in] phase Index corresponding to the phase + //! \param[in] force Correction force from the particles in a cell + void update_correction_force(bool update, unsigned phase, + const VectorDim& force) noexcept override; + + //! Return correction force at a given node for a given phase + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + VectorDim correction_force(unsigned phase) const override { + return correction_force_.col(phase); + } + + /**@}*/ + private: //! Mutex SpinMutex node_mutex_; @@ -279,6 +454,8 @@ class Node : public NodeBase { unsigned dof_{std::numeric_limits::max()}; //! Status bool status_{false}; + //! Solving status + bool solving_status_{false}; //! Mass Eigen::Matrix mass_; //! Volume @@ -290,7 +467,7 @@ class Node : public NodeBase { //! Pressure Eigen::Matrix pressure_; //! Displacement - Eigen::Matrix contact_displacement_; + VectorDim contact_displacement_; //! Velocity Eigen::Matrix velocity_; //! Momentum @@ -299,6 +476,8 @@ class Node : public NodeBase { Eigen::Matrix acceleration_; //! Velocity constraints std::map velocity_constraints_; + //! Pressure constraint + std::map pressure_constraints_; //! Rotation matrix for general velocity constraints Eigen::Matrix rotation_matrix_; //! Material ids whose information was passed to this node @@ -309,6 +488,8 @@ class Node : public NodeBase { //! Frictional constraints bool friction_{false}; std::tuple friction_constraint_; + //! Mathematical function for pressure + std::map> pressure_function_; //! Concentrated force Eigen::Matrix concentrated_force_; //! Mathematical function for force @@ -319,9 +500,32 @@ class Node : public NodeBase { std::unique_ptr console_; //! MPI ranks std::set mpi_ranks_; + //! Global index for active node (in each rank) + Index active_id_{std::numeric_limits::max()}; + //! Global index for active node (globally) + Index global_active_id_{std::numeric_limits::max()}; + + /** + * \defgroup MultiPhaseVariables Variables for multi-phase MPM + * @{ + */ + //! Free surface + bool free_surface_{false}; + //! Signed distance + double signed_distance_; + //! Interpolated density + Eigen::Matrix density_; + //! p^(t+1) - beta * p^(t) + double pressure_increment_; + //! Correction force + Eigen::Matrix correction_force_; + //! Drag force + Eigen::Matrix drag_force_coefficient_; + /**@}*/ }; // Node class } // namespace mpm #include "node.tcc" +#include "node_multiphase.tcc" #endif // MPM_NODE_H_ diff --git a/include/node.tcc b/include/node.tcc index 7d3a9cfcb..838127737 100644 --- a/include/node.tcc +++ b/include/node.tcc @@ -32,7 +32,11 @@ void mpm::Node::initialise() noexcept { velocity_.setZero(); momentum_.setZero(); acceleration_.setZero(); + free_surface_ = false; + pressure_increment_ = 0.; + correction_force_.setZero(); status_ = false; + solving_status_ = false; material_ids_.clear(); } @@ -172,6 +176,49 @@ void mpm::Node::update_mass_pressure( } } +//! Assign pressure constraint +template +bool mpm::Node::assign_pressure_constraint( + unsigned phase, double pressure, + const std::shared_ptr& function) { + bool status = true; + try { + // Constrain directions can take values between 0 and Tnphases + if (phase < Tnphases) { + this->pressure_constraints_.insert(std::make_pair( + static_cast(phase), static_cast(pressure))); + // Assign pressure function + if (function != nullptr) + this->pressure_function_.insert( + std::make_pair>( + static_cast(phase), + static_cast>(function))); + } else + throw std::runtime_error("Pressure constraint phase is invalid"); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Apply pressure constraint +template +void mpm::Node::apply_pressure_constraint( + unsigned phase, double dt, Index step) noexcept { + // Assert + assert(phase < Tnphases); + + if (pressure_constraints_.find(phase) != pressure_constraints_.end()) { + const double scalar = + (pressure_function_.find(phase) != pressure_function_.end()) + ? pressure_function_[phase]->value(step * dt) + : 1.0; + this->pressure_(phase) = scalar * pressure_constraints_[phase]; + } +} + //! Assign pressure at the nodes from particle template void mpm::Node::assign_pressure(unsigned phase, @@ -554,8 +601,8 @@ void mpm::Node::update_property( template void mpm::Node::compute_multimaterial_change_in_momentum() { - // iterate over all materials in the material_ids set and update the change in - // momentum + // iterate over all materials in the material_ids set and update the change + // in momentum node_mutex_.lock(); for (auto mitr = material_ids_.begin(); mitr != material_ids_.end(); ++mitr) { const Eigen::Matrix mass = @@ -563,7 +610,7 @@ void mpm::Node momentum = property_handle_->property("momenta", prop_id_, *mitr, Tdim); const Eigen::Matrix change_in_momenta = - velocity_ * mass - momentum; + velocity_.col(mpm::NodePhase::NSolid) * mass - momentum; property_handle_->update_property("change_in_momenta", prop_id_, *mitr, change_in_momenta, Tdim); } @@ -575,8 +622,8 @@ template void mpm::Node::compute_multimaterial_separation_vector() { // iterate over all materials in the material_ids set, update the - // displacements and calculate the displacement of the center of mass for this - // node + // displacements and calculate the displacement of the center of mass for + // this node node_mutex_.lock(); for (auto mitr = material_ids_.begin(); mitr != material_ids_.end(); ++mitr) { const auto& material_displacement = @@ -586,8 +633,8 @@ void mpm::Nodeassign_property( "displacements", prop_id_, *mitr, material_displacement / material_mass(0, 0), Tdim); @@ -630,4 +677,4 @@ void mpm::Node& function) = 0; + //! Assign velocity constraint //! Directions can take values between 0 and Dim * Nphases //! \param[in] dir Direction of velocity constraint @@ -253,6 +283,132 @@ class NodeBase { //! Compute multimaterial normal unit vector virtual void compute_multimaterial_normal_unit_vector() = 0; + /** + * \defgroup MultiPhase Functions dealing with multi-phase MPM + */ + /**@{*/ + + //! Return interpolated density at a given node for a given phase + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + virtual double density(unsigned phase) const = 0; + + //! Compute nodal density + //! \ingroup MultiPhase + virtual void compute_density() = 0; + + //! Assign free surface + //! \ingroup MultiPhase + virtual void assign_free_surface(bool free_surface) = 0; + + //! Return free surface bool + //! \ingroup MultiPhase + virtual bool free_surface() const = 0; + + //! Initialise two-phase nodal properties + //! \ingroup MultiPhase + virtual void initialise_twophase() noexcept = 0; + + //! Update internal force (body force / traction force) + //! \ingroup MultiPhase + //! \param[in] update A boolean to update (true) or assign (false) + //! \param[in] drag_force Drag force from the particles in a cell + //! \retval status Update status + virtual void update_drag_force_coefficient(bool update, + const VectorDim& drag_force) = 0; + + //! Return drag force at a given node + //! \ingroup MultiPhase + virtual VectorDim drag_force_coefficient() const = 0; + + //! Compute acceleration and velocity for two phase + //! \ingroup MultiPhase + //! \param[in] dt Timestep in analysis + virtual bool compute_acceleration_velocity_twophase_explicit( + double dt) noexcept = 0; + + //! Compute acceleration and velocity for two phase with cundall damping + //! \ingroup MultiPhase + //! \param[in] dt Timestep in analysis + virtual bool compute_acceleration_velocity_twophase_explicit_cundall( + double dt, double damping_factor) noexcept = 0; + + //! Compute semi-implicit acceleration and velocity + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + //! \param[in] dt Timestep in analysis + //! \details Can be used for both semi-implicit navier-stokes and two-phase + //! solvers + //! \retval status Computation status + virtual bool compute_acceleration_velocity_semi_implicit_corrector( + unsigned phase, double dt) = 0; + + //! Compute semi-implicit acceleration and velocity with Cundall damping + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + //! \param[in] dt Timestep in analysis + //! \details Can be used for both semi-implicit navier-stokes and two-phase + //! solvers + //! \retval status Computation status + virtual bool compute_acceleration_velocity_semi_implicit_corrector_cundall( + unsigned phase, double dt, double damping_factor) = 0; + + //! Assign active id + //! \ingroup MultiPhase + virtual void assign_active_id(Index id) = 0; + + //! Return active id + //! \ingroup MultiPhase + virtual mpm::Index active_id() const = 0; + + //! Assign global active id + //! \ingroup MultiPhase + virtual void assign_global_active_id(Index id) = 0; + + //! Return global active id + //! \ingroup MultiPhase + virtual mpm::Index global_active_id() const = 0; + + //! Return pressure constraint + //! \ingroup MultiPhase + virtual double pressure_constraint(const unsigned phase, + const double current_time) const = 0; + + //! Update pressure increment at the node + //! \ingroup MultiPhase + virtual void update_pressure_increment( + const Eigen::VectorXd& pressure_increment, unsigned phase, + double current_time = 0.) = 0; + + //! Return nodal pressure increment + //! \ingroup MultiPhase + virtual double pressure_increment() const = 0; + + //! Return map of velocity constraints + //! \ingroup MultiPhase + virtual std::map& velocity_constraints() = 0; + + //! Update intermediate velocity at the node + //! \ingroup MultiPhase + virtual void update_intermediate_acceleration_velocity( + const unsigned phase, const Eigen::MatrixXd& acceleration_inter, + double dt) = 0; + + //! Update correction force + //! \ingroup MultiPhase + //! \param[in] update A boolean to update (true) or assign (false) + //! \param[in] phase Index corresponding to the phase + //! \param[in] force Correction force from the particles in a cell + virtual void update_correction_force(bool update, unsigned phase, + const VectorDim& force) noexcept = 0; + + //! Return correction force + //! \ingroup MultiPhase + //! \param[in] phase Index corresponding to the phase + virtual VectorDim correction_force(unsigned phase) const = 0; + + /**@}*/ + }; // NodeBase class } // namespace mpm diff --git a/include/node_multiphase.tcc b/include/node_multiphase.tcc new file mode 100644 index 000000000..606920ff2 --- /dev/null +++ b/include/node_multiphase.tcc @@ -0,0 +1,283 @@ +//! Compute mass density (Z. Wiezckowski, 2004) +//! density = mass / lumped volume +template +void mpm::Node::compute_density() { + const double tolerance = 1.E-16; // std::numeric_limits::lowest(); + + for (unsigned phase = 0; phase < Tnphases; ++phase) { + if (mass_(phase) > tolerance) { + if (volume_(phase) > tolerance) + density_(phase) = mass_(phase) / volume_(phase); + + // Check to see if value is below threshold + if (std::abs(density_(phase)) < tolerance) density_(phase) = 0.; + } + } +} + +//! Initialise two-phase nodal properties +template +void mpm::Node::initialise_twophase() noexcept { + this->initialise(); + // Specific variables for two phase + drag_force_coefficient_.setZero(); +} + +//! Update drag force coefficient +template +void mpm::Node::update_drag_force_coefficient( + bool update, const Eigen::Matrix& drag_force_coefficient) { + + // Decide to update or assign + const double factor = (update == true) ? 1. : 0.; + + // Update/assign drag force coefficient + node_mutex_.lock(); + drag_force_coefficient_ = + drag_force_coefficient_ * factor + drag_force_coefficient; + node_mutex_.unlock(); +} + +//! Compute acceleration and velocity for two phase +template +bool mpm::Node:: + compute_acceleration_velocity_twophase_explicit(double dt) noexcept { + bool status = false; + const double tolerance = 1.0E-15; + if (this->mass(mpm::NodePhase::NSolid) > tolerance && + this->mass(mpm::NodePhase::NLiquid) > tolerance) { + // Compute drag force + VectorDim drag_force = drag_force_coefficient_.cwiseProduct( + velocity_.col(mpm::NodePhase::NLiquid) - + velocity_.col(mpm::NodePhase::NSolid)); + + // Acceleration of pore fluid (momentume balance of fluid phase) + this->acceleration_.col(mpm::NodePhase::NLiquid) = + (this->external_force_.col(mpm::NodePhase::NLiquid) + + this->internal_force_.col(mpm::NodePhase::NLiquid) - drag_force) / + this->mass_(mpm::NodePhase::NLiquid); + + // Acceleration of solid skeleton (momentume balance of mixture) + this->acceleration_.col(mpm::NodePhase::NSolid) = + (this->external_force_.col(mpm::NodePhase::NMixture) + + this->internal_force_.col(mpm::NodePhase::NMixture) - + this->mass_(mpm::NodePhase::NLiquid) * + this->acceleration_.col(mpm::NodePhase::NLiquid)) / + this->mass_(mpm::NodePhase::NSolid); + + // Apply friction constraints + this->apply_friction_constraints(dt); + + // Velocity += acceleration * dt + this->velocity_ += this->acceleration_ * dt; + + // Apply velocity constraints, which also sets acceleration to 0, + // when velocity is set. + this->apply_velocity_constraints(); + + // Set a threshold + for (unsigned i = 0; i < Tdim; ++i) { + if (std::abs(velocity_.col(mpm::NodePhase::NSolid)(i)) < tolerance) + velocity_.col(mpm::NodePhase::NSolid)(i) = 0.; + if (std::abs(acceleration_.col(mpm::NodePhase::NSolid)(i)) < tolerance) + acceleration_.col(mpm::NodePhase::NSolid)(i) = 0.; + if (std::abs(velocity_.col(mpm::NodePhase::NLiquid)(i)) < tolerance) + velocity_.col(mpm::NodePhase::NLiquid)(i) = 0.; + if (std::abs(acceleration_.col(mpm::NodePhase::NLiquid)(i)) < tolerance) + acceleration_.col(mpm::NodePhase::NLiquid)(i) = 0.; + } + status = true; + } + return status; +} + +//! Compute acceleration and velocity for two phase with damping +template +bool mpm::Node:: + compute_acceleration_velocity_twophase_explicit_cundall( + double dt, double damping_factor) noexcept { + bool status = false; + const double tolerance = 1.0E-15; + + if (this->mass(mpm::NodePhase::NSolid) > tolerance && + this->mass(mpm::NodePhase::NLiquid) > tolerance) { + // Compute drag force + VectorDim drag_force = drag_force_coefficient_.cwiseProduct( + velocity_.col(mpm::NodePhase::NLiquid) - + velocity_.col(mpm::NodePhase::NSolid)); + + // Unbalanced force of liquid phase + auto unbalanced_force_liquid = + this->external_force_.col(mpm::NodePhase::NLiquid) + + this->internal_force_.col(mpm::NodePhase::NLiquid) - drag_force; + // Acceleration of liquid phase (momentume balance of fluid phase) + this->acceleration_.col(mpm::NodePhase::NLiquid) = + (unbalanced_force_liquid - + damping_factor * unbalanced_force_liquid.norm() * + this->velocity_.col(mpm::NodePhase::NLiquid).cwiseSign()) / + this->mass(mpm::NodePhase::NLiquid); + + // Unbalanced force of solid phase + auto unbalanced_force_solid = + this->external_force_.col(mpm::NodePhase::NMixture) + + this->internal_force_.col(mpm::NodePhase::NMixture) - + this->mass_(mpm::NodePhase::NLiquid) * + this->acceleration_.col(mpm::NodePhase::NLiquid); + // Acceleration of solid phase (momentume balance of mixture) + this->acceleration_.col(mpm::NodePhase::NSolid) = + (unbalanced_force_solid - + damping_factor * unbalanced_force_solid.norm() * + this->velocity_.col(mpm::NodePhase::NSolid).cwiseSign()) / + this->mass(mpm::NodePhase::NSolid); + + // Apply friction constraints + this->apply_friction_constraints(dt); + + // Velocity += acceleration * dt + this->velocity_ += this->acceleration_ * dt; + + // Apply velocity constraints, which also sets acceleration to 0, + // when velocity is set. + this->apply_velocity_constraints(); + + // Set a threshold + for (unsigned i = 0; i < Tdim; ++i) { + if (std::abs(velocity_.col(mpm::NodePhase::NSolid)(i)) < tolerance) + velocity_.col(mpm::NodePhase::NSolid)(i) = 0.; + if (std::abs(acceleration_.col(mpm::NodePhase::NSolid)(i)) < tolerance) + acceleration_.col(mpm::NodePhase::NSolid)(i) = 0.; + if (std::abs(velocity_.col(mpm::NodePhase::NLiquid)(i)) < tolerance) + velocity_.col(mpm::NodePhase::NLiquid)(i) = 0.; + if (std::abs(acceleration_.col(mpm::NodePhase::NLiquid)(i)) < tolerance) + acceleration_.col(mpm::NodePhase::NLiquid)(i) = 0.; + } + status = true; + } + return status; +} + +//! Compute semi-implicit acceleration and velocity +template +bool mpm::Node:: + compute_acceleration_velocity_semi_implicit_corrector(unsigned phase, + double dt) { + bool status = false; + const double tolerance = std::numeric_limits::min(); + if (mass_(phase) > tolerance) { + Eigen::Matrix acceleration_corrected = + correction_force_.col(phase) / mass_(phase); + + // Acceleration + this->acceleration_.col(phase) += acceleration_corrected; + + // Update velocity + velocity_.col(phase) += acceleration_corrected * dt; + + // Apply friction constraints + this->apply_friction_constraints(dt); + + // Apply velocity constraints, which also sets acceleration to 0, + // when velocity is set. + this->apply_velocity_constraints(); + + // Set a threshold + for (unsigned i = 0; i < Tdim; ++i) { + if (std::abs(velocity_.col(phase)(i)) < tolerance) + velocity_.col(phase)(i) = 0.; + if (std::abs(acceleration_.col(phase)(i)) < tolerance) + acceleration_.col(phase)(i) = 0.; + } + + status = true; + } + + return status; +} + +//! Compute semi-implicit acceleration and velocity with cundall damping +//! factor +template +bool mpm::Node:: + compute_acceleration_velocity_semi_implicit_corrector_cundall( + unsigned phase, double dt, double damping_factor) { + bool status = false; + const double tolerance = std::numeric_limits::min(); + + if (mass_(phase) > tolerance) { + // Unbalance force + auto unbalanced_force = correction_force_.col(phase); + + // Semi-implicit solver + auto acceleration_corrected = + (unbalanced_force - damping_factor * unbalanced_force.norm() * + this->velocity_.col(phase).cwiseSign()) / + this->mass_(phase); + + // Acceleration + this->acceleration_.col(phase) = acceleration_corrected; + + // Update velocity + velocity_.col(phase) += acceleration_corrected * dt; + + // Apply friction constraints + this->apply_friction_constraints(dt); + + // Apply velocity constraints, which also sets acceleration to 0, + // when velocity is set. + this->apply_velocity_constraints(); + + // Set a threshold + for (unsigned i = 0; i < Tdim; ++i) { + if (std::abs(velocity_.col(phase)(i)) < tolerance) + velocity_.col(phase)(i) = 0.; + if (std::abs(acceleration_.col(phase)(i)) < tolerance) + acceleration_.col(phase)(i) = 0.; + } + + status = true; + } + + return status; +} + +//! Update pressure increment at the node +template +void mpm::Node::update_pressure_increment( + const Eigen::VectorXd& pressure_increment, unsigned phase, + double current_time) { + this->pressure_increment_ = pressure_increment(active_id_); + + // If pressure boundary, increment is zero + if (pressure_constraints_.find(phase) != pressure_constraints_.end() || + this->free_surface()) + this->pressure_increment_ = 0; +} + +//! Update intermediate acceleration and velocity at the node +template +void mpm::Node::update_intermediate_acceleration_velocity( + const unsigned phase, const Eigen::MatrixXd& acceleration_inter, + double dt) { + // Update nodal intermediate acceleration + acceleration_.col(phase) = acceleration_inter.row(active_id_).transpose(); + + // Update nodal intermediate velocity + velocity_.col(phase) += dt * acceleration_inter.row(active_id_).transpose(); +} + +//! Update correction force +template +void mpm::Node::update_correction_force( + bool update, unsigned phase, + const Eigen::Matrix& force) noexcept { + // Assert + assert(phase < Tnphases); + + // Decide to update or assign + const double factor = (update == true) ? 1. : 0.; + + // Update/assign correction force + node_mutex_.lock(); + correction_force_.col(phase) = correction_force_.col(phase) * factor + force; + node_mutex_.unlock(); +} diff --git a/include/particles/particle.h b/include/particles/particle.h index a362d7c76..314556f8b 100644 --- a/include/particles/particle.h +++ b/include/particles/particle.h @@ -46,32 +46,23 @@ class Particle : public ParticleBase { //! Delete assignment operator Particle& operator=(const Particle&) = delete; - //! Initialise particle from HDF5 data - //! \param[in] particle HDF5 data of particle - //! \retval status Status of reading HDF5 particle - bool initialise_particle(const HDF5Particle& particle) override; - - //! Initialise particle HDF5 data and material - //! \param[in] particle HDF5 data of particle - //! \param[in] material Material associated with the particle - //! \retval status Status of reading HDF5 particle + //! Initialise particle from POD data + //! \param[in] particle POD data of particle + //! \retval status Status of reading POD particle + bool initialise_particle(PODParticle& particle) override; + + //! Initialise particle POD data and material + //! \param[in] particle POD data of particle + //! \param[in] materials Material associated with the particle arranged in a + //! vector + //! \retval status Status of reading POD particle virtual bool initialise_particle( - const HDF5Particle& particle, - const std::shared_ptr>& material) override; + PODParticle& particle, + const std::vector>>& materials) override; - //! Assign material history variables - //! \param[in] state_vars State variables - //! \param[in] material Material associated with the particle - //! \param[in] phase Index to indicate material phase - //! \retval status Status of cloning HDF5 particle - bool assign_material_state_vars( - const mpm::dense_map& state_vars, - const std::shared_ptr>& material, - unsigned phase = mpm::ParticlePhase::Solid) override; - - //! Retrun particle data as HDF5 - //! \retval particle HDF5 data of the particle - HDF5Particle hdf5() const override; + //! Return particle data as POD + //! \retval particle POD of the particle + std::shared_ptr pod() const override; //! Initialise properties void initialise() override; @@ -121,6 +112,9 @@ class Particle : public ParticleBase { //! Return volume double volume() const override { return volume_; } + //! Return the approximate particle diameter + double diameter() const override; + //! Return size of particle in natural coordinates VectorDim natural_size() const override { return natural_size_; } @@ -131,7 +125,6 @@ class Particle : public ParticleBase { void update_volume() noexcept override; //! Return mass density - //! \param[in] phase Index corresponding to the phase double mass_density() const override { return mass_density_; } //! Compute mass as volume * density @@ -222,7 +215,6 @@ class Particle : public ParticleBase { bool assign_traction(unsigned direction, double traction) override; //! Return traction of the particle - //! \param[in] phase Index corresponding to the phase VectorDim traction() const override { return traction_; } //! Map traction force @@ -234,6 +226,24 @@ class Particle : public ParticleBase { void compute_updated_position(double dt, bool velocity_update = false) noexcept override; + //! Assign material history variables + //! \param[in] state_vars State variables + //! \param[in] material Material associated with the particle + //! \param[in] phase Index to indicate material phase + //! \retval status Status of assigning material state variables + bool assign_material_state_vars( + const mpm::dense_map& state_vars, + const std::shared_ptr>& material, + unsigned phase = mpm::ParticlePhase::Solid) override; + + //! Assign a state variable + //! \param[in] var State variable + //! \param[in] value State variable to be assigned + //! \param[in] phase Index to indicate phase + void assign_state_variable( + const std::string& var, double value, + unsigned phase = mpm::ParticlePhase::Solid) override; + //! Return a state variable //! \param[in] var State variable //! \param[in] phase Index to indicate phase @@ -256,6 +266,14 @@ class Particle : public ParticleBase { bool compute_pressure_smoothing( unsigned phase = mpm::ParticlePhase::Solid) noexcept override; + //! Assign a state variable + //! \param[in] value Particle pressure to be assigned + //! \param[in] phase Index to indicate phase + void assign_pressure(double pressure, + unsigned phase = mpm::ParticlePhase::Solid) override { + this->assign_state_variable("pressure", pressure, phase); + } + //! Return pressure of the particles //! \param[in] phase Index to indicate phase double pressure(unsigned phase = mpm::ParticlePhase::Solid) const override { @@ -287,6 +305,29 @@ class Particle : public ParticleBase { //! Assign material id of this particle to nodes void append_material_id_to_nodes() const override; + //! Assign free surface + void assign_free_surface(bool free_surface) override { + free_surface_ = free_surface; + }; + + //! Return free surface bool + bool free_surface() const override { return free_surface_; }; + + //! Compute free surface in particle level by density ratio comparison + //! \param[in] density_ratio_tolerance Tolerance of density ratio comparison. + //! Default value is set to be 0.65, which is derived from a 3D case where at + //! one side the cell is fully occupied by particles and the other side the + //! cell is empty. See (Hamad, 2015). + //! \retval status Status of compute_free_surface + bool compute_free_surface_by_density( + double density_ratio_tolerance = 0.65) override; + + //! Assign normal vector + void assign_normal(const VectorDim& normal) override { normal_ = normal; }; + + //! Return normal vector + VectorDim normal() const override { return normal_; }; + //! Return the number of neighbour particles unsigned nneighbours() const override { return neighbours_.size(); }; @@ -321,7 +362,6 @@ class Particle : public ParticleBase { //! \param[in] phase_size The material phase size void initialise_material(unsigned phase_size = 1); - private: //! Compute strain rate //! \param[in] dn_dx The spatial gradient of shape function //! \param[in] phase Index to indicate phase @@ -331,9 +371,8 @@ class Particle : public ParticleBase { //! Compute pack size //! \retval pack size of serialized object - int compute_pack_size() const; + virtual int compute_pack_size() const; - private: //! particle id using ParticleBase::id_; //! coordinates @@ -384,6 +423,10 @@ class Particle : public ParticleBase { Eigen::Matrix displacement_; //! Particle velocity constraints std::map particle_velocity_constraints_; + //! Free surface + bool free_surface_{false}; + //! Free surface + Eigen::Matrix normal_; //! Set traction bool set_traction_{false}; //! Surface Traction (given as a stress; force/area) diff --git a/include/particles/particle.tcc b/include/particles/particle.tcc index b50a5eb56..aafa7a650 100644 --- a/include/particles/particle.tcc +++ b/include/particles/particle.tcc @@ -30,9 +30,9 @@ mpm::Particle::Particle(Index id, const VectorDim& coord, bool status) console_ = std::make_unique(logger, mpm::stdout_sink); } -//! Initialise particle data from HDF5 +//! Initialise particle data from POD template -bool mpm::Particle::initialise_particle(const HDF5Particle& particle) { +bool mpm::Particle::initialise_particle(PODParticle& particle) { // Assign id this->id_ = particle.id; @@ -107,16 +107,20 @@ bool mpm::Particle::initialise_particle(const HDF5Particle& particle) { return true; } -//! Initialise particle data from HDF5 +//! Initialise particle data from POD template bool mpm::Particle::initialise_particle( - const HDF5Particle& particle, - const std::shared_ptr>& material) { + PODParticle& particle, + const std::vector>>& materials) { bool status = this->initialise_particle(particle); - if (material != nullptr) { - if (this->material_id() == material->id() || + + assert(materials.size() == 1); + + if (materials.at(mpm::ParticlePhase::Solid) != nullptr) { + if (this->material_id() == materials.at(mpm::ParticlePhase::Solid)->id() || this->material_id() == std::numeric_limits::max()) { - bool assign_mat = this->assign_material(material); + bool assign_mat = + this->assign_material(materials.at(mpm::ParticlePhase::Solid)); if (!assign_mat) throw std::runtime_error("Material assignment failed"); // Reinitialize state variables auto mat_state_vars = (this->material())->initialise_state_variables(); @@ -137,12 +141,12 @@ bool mpm::Particle::initialise_particle( return status; } -//! Return particle data in HDF5 format +//! Return particle data as POD template // cppcheck-suppress * -mpm::HDF5Particle mpm::Particle::hdf5() const { - - mpm::HDF5Particle particle_data; +std::shared_ptr mpm::Particle::pod() const { + // Initialise particle data + auto particle_data = std::make_shared(); Eigen::Vector3d coordinates; coordinates.setZero(); @@ -166,63 +170,63 @@ mpm::HDF5Particle mpm::Particle::hdf5() const { Eigen::Matrix strain = this->strain_; - particle_data.id = this->id(); - particle_data.mass = this->mass(); - particle_data.volume = this->volume(); - particle_data.pressure = + particle_data->id = this->id(); + particle_data->mass = this->mass(); + particle_data->volume = this->volume(); + particle_data->pressure = (state_variables_[mpm::ParticlePhase::Solid].find("pressure") != state_variables_[mpm::ParticlePhase::Solid].end()) ? state_variables_[mpm::ParticlePhase::Solid].at("pressure") : 0.; - particle_data.coord_x = coordinates[0]; - particle_data.coord_y = coordinates[1]; - particle_data.coord_z = coordinates[2]; + particle_data->coord_x = coordinates[0]; + particle_data->coord_y = coordinates[1]; + particle_data->coord_z = coordinates[2]; - particle_data.displacement_x = displacement[0]; - particle_data.displacement_y = displacement[1]; - particle_data.displacement_z = displacement[2]; + particle_data->displacement_x = displacement[0]; + particle_data->displacement_y = displacement[1]; + particle_data->displacement_z = displacement[2]; - particle_data.nsize_x = nsize[0]; - particle_data.nsize_y = nsize[1]; - particle_data.nsize_z = nsize[2]; + particle_data->nsize_x = nsize[0]; + particle_data->nsize_y = nsize[1]; + particle_data->nsize_z = nsize[2]; - particle_data.velocity_x = velocity[0]; - particle_data.velocity_y = velocity[1]; - particle_data.velocity_z = velocity[2]; + particle_data->velocity_x = velocity[0]; + particle_data->velocity_y = velocity[1]; + particle_data->velocity_z = velocity[2]; - particle_data.stress_xx = stress[0]; - particle_data.stress_yy = stress[1]; - particle_data.stress_zz = stress[2]; - particle_data.tau_xy = stress[3]; - particle_data.tau_yz = stress[4]; - particle_data.tau_xz = stress[5]; + particle_data->stress_xx = stress[0]; + particle_data->stress_yy = stress[1]; + particle_data->stress_zz = stress[2]; + particle_data->tau_xy = stress[3]; + particle_data->tau_yz = stress[4]; + particle_data->tau_xz = stress[5]; - particle_data.strain_xx = strain[0]; - particle_data.strain_yy = strain[1]; - particle_data.strain_zz = strain[2]; - particle_data.gamma_xy = strain[3]; - particle_data.gamma_yz = strain[4]; - particle_data.gamma_xz = strain[5]; + particle_data->strain_xx = strain[0]; + particle_data->strain_yy = strain[1]; + particle_data->strain_zz = strain[2]; + particle_data->gamma_xy = strain[3]; + particle_data->gamma_yz = strain[4]; + particle_data->gamma_xz = strain[5]; - particle_data.epsilon_v = this->volumetric_strain_centroid_; + particle_data->epsilon_v = this->volumetric_strain_centroid_; - particle_data.status = this->status(); + particle_data->status = this->status(); - particle_data.cell_id = this->cell_id(); + particle_data->cell_id = this->cell_id(); - particle_data.material_id = this->material_id(); + particle_data->material_id = this->material_id(); // Write state variables if (this->material() != nullptr) { - particle_data.nstate_vars = + particle_data->nstate_vars = state_variables_[mpm::ParticlePhase::Solid].size(); if (state_variables_[mpm::ParticlePhase::Solid].size() > 20) throw std::runtime_error("# of state variables cannot be more than 20"); unsigned i = 0; auto state_variables = (this->material())->state_variables(); for (const auto& state_var : state_variables) { - particle_data.svars[i] = + particle_data->svars[i] = state_variables_[mpm::ParticlePhase::Solid].at(state_var); ++i; } @@ -245,6 +249,7 @@ void mpm::Particle::initialise() { stress_.setZero(); traction_.setZero(); velocity_.setZero(); + normal_.setZero(); volume_ = std::numeric_limits::max(); volumetric_strain_centroid_ = 0.; @@ -254,6 +259,7 @@ void mpm::Particle::initialise() { this->scalar_properties_["mass_density"] = [&]() { return mass_density(); }; this->vector_properties_["displacements"] = [&]() { return displacement(); }; this->vector_properties_["velocities"] = [&]() { return velocity(); }; + this->vector_properties_["normals"] = [&]() { return normal(); }; this->tensor_properties_["stresses"] = [&]() { return stress(); }; this->tensor_properties_["strains"] = [&]() { return strain(); }; } @@ -289,6 +295,14 @@ bool mpm::Particle::assign_material_state_vars( return status; } +//! Assign a state variable +template +void mpm::Particle::assign_state_variable(const std::string& var, + double value, unsigned phase) { + assert(state_variables_[phase].find(var) != state_variables_[phase].end()); + state_variables_[phase].at(var) = value; +} + // Assign a cell to particle template bool mpm::Particle::assign_cell( @@ -504,6 +518,15 @@ void mpm::Particle::update_volume() noexcept { this->mass_density_ = this->mass_density_ / (1. + dvolumetric_strain_); } +//! Return the approximate particle diameter +template +double mpm::Particle::diameter() const { + double diameter = 0.; + if (Tdim == 2) diameter = 2.0 * std::sqrt(volume_ / M_PI); + if (Tdim == 3) diameter = 2.0 * std::pow(volume_ * 0.75 / M_PI, (1 / 3)); + return diameter; +} + // Compute mass of particle template void mpm::Particle::compute_mass() noexcept { @@ -847,6 +870,10 @@ bool mpm::Particle::compute_pressure_smoothing(unsigned phase) noexcept { pressure += shapefn_[i] * nodes_[i]->pressure(phase); state_variables_[phase]["pressure"] = pressure; + + // If free_surface particle, overwrite pressure to zero + if (free_surface_) state_variables_[phase]["pressure"] = 0.0; + status = true; } return status; @@ -899,6 +926,27 @@ void mpm::Particle::append_material_id_to_nodes() const { nodes_[i]->append_material_id(this->material_id()); } +//! Compute free surface in particle level by density ratio comparison +template +bool mpm::Particle::compute_free_surface_by_density( + double density_ratio_tolerance) { + bool status = false; + // Check if particle has a valid cell ptr + if (cell_ != nullptr) { + // Simple approach of density comparison (Hamad, 2015) + // Get interpolated nodal density + double nodal_mass_density = 0; + for (unsigned i = 0; i < nodes_.size(); ++i) + nodal_mass_density += + shapefn_[i] * nodes_[i]->density(mpm::ParticlePhase::Solid); + + // Compare smoothen density to actual particle mass density + if ((nodal_mass_density / mass_density_) <= density_ratio_tolerance) + status = true; + } + return status; +}; + //! Assign neighbour particles template void mpm::Particle::assign_neighbours( @@ -1040,9 +1088,10 @@ std::vector mpm::Particle::serialize() { MPI_COMM_WORLD); // state variables - if (this->material() != nullptr) { + if (this->material(mpm::ParticlePhase::Solid) != nullptr) { std::vector svars; - auto state_variables = (this->material())->state_variables(); + auto state_variables = + (this->material(mpm::ParticlePhase::Solid))->state_variables(); for (const auto& state_var : state_variables) svars.emplace_back( state_variables_[mpm::ParticlePhase::Solid].at(state_var)); diff --git a/include/particles/particle_base.h b/include/particles/particle_base.h index dcb2bc419..a604c96b7 100644 --- a/include/particles/particle_base.h +++ b/include/particles/particle_base.h @@ -14,8 +14,9 @@ #include "cell.h" #include "data_types.h" #include "function_base.h" -#include "hdf5_particle.h" #include "material.h" +#include "pod_particle.h" +#include "pod_particle_twophase.h" namespace mpm { @@ -24,11 +25,18 @@ template class Material; //! Particle phases -enum ParticlePhase : unsigned int { Solid = 0, Liquid = 1, Gas = 2 }; +enum ParticlePhase : unsigned int { + SinglePhase = 0, + Mixture = 0, + Solid = 0, + Liquid = 1, + Gas = 2 +}; //! Particle type extern std::map ParticleType; extern std::map ParticleTypeName; +extern std::map ParticlePODTypeName; //! ParticleBase class //! \brief Base class that stores the information about particleBases @@ -60,32 +68,23 @@ class ParticleBase { //! Delete assignement operator ParticleBase& operator=(const ParticleBase&) = delete; - //! Initialise particle HDF5 data - //! \param[in] particle HDF5 data of particle - //! \retval status Status of reading HDF5 particle - virtual bool initialise_particle(const HDF5Particle& particle) = 0; + //! Initialise particle POD data + //! \param[in] particle POD data of particle + //! \retval status Status of reading POD particle + virtual bool initialise_particle(PODParticle& particle) = 0; - //! Initialise particle HDF5 data and material - //! \param[in] particle HDF5 data of particle - //! \param[in] material Material associated with the particle - //! \retval status Status of reading HDF5 particle + //! Initialise particle POD data and material + //! \param[in] particle POD data of particle + //! \param[in] materials Material associated with the particle arranged in a + //! vector + //! \retval status Status of reading POD particle virtual bool initialise_particle( - const HDF5Particle& particle, - const std::shared_ptr>& material) = 0; + PODParticle& particle, + const std::vector>>& materials) = 0; - //! Assign material history variables - //! \param[in] state_vars State variables - //! \param[in] material Material associated with the particle - //! \param[in] phase Index to indicate material phase - //! \retval status Status of cloning HDF5 particle - virtual bool assign_material_state_vars( - const mpm::dense_map& state_vars, - const std::shared_ptr>& material, - unsigned phase = mpm::ParticlePhase::Solid) = 0; - - //! Retrun particle data as HDF5 - //! \retval particle HDF5 data of the particle - virtual HDF5Particle hdf5() const = 0; + //! Return particle data as POD + //! \retval particle POD of the particle + virtual std::shared_ptr pod() const = 0; //! Return id of the particleBase Index id() const { return id_; } @@ -132,6 +131,9 @@ class ParticleBase { //! Return volume virtual double volume() const = 0; + //! Return the approximate particle diameter + virtual double diameter() const = 0; + //! Return size of particle in natural coordinates virtual VectorDim natural_size() const = 0; @@ -176,6 +178,12 @@ class ParticleBase { return material_id_[phase]; } + //! Assign material state variables + virtual bool assign_material_state_vars( + const mpm::dense_map& state_vars, + const std::shared_ptr>& material, + unsigned phase = mpm::ParticlePhase::Solid) = 0; + //! Return state variables //! \param[in] phase Index to indicate material phase mpm::dense_map state_variables( @@ -183,6 +191,16 @@ class ParticleBase { return state_variables_[phase]; } + //! Assign a state variable + virtual void assign_state_variable( + const std::string& var, double value, + unsigned phase = mpm::ParticlePhase::Solid) = 0; + + //! Return a state variable + virtual double state_variable( + const std::string& var, + unsigned phase = mpm::ParticlePhase::Solid) const = 0; + //! Assign status void assign_status(bool status) { status_ = status; } @@ -198,6 +216,10 @@ class ParticleBase { //! Return mass virtual double mass() const = 0; + //! Assign pressure + virtual void assign_pressure(double pressure, + unsigned phase = mpm::ParticlePhase::Solid) = 0; + //! Return pressure virtual double pressure(unsigned phase = mpm::ParticlePhase::Solid) const = 0; @@ -217,7 +239,7 @@ class ParticleBase { virtual double dvolumetric_strain() const = 0; //! Initial stress - virtual void initial_stress(const Eigen::Matrix&) = 0; + virtual void initial_stress(const Eigen::Matrix& stress) = 0; //! Compute stress virtual void compute_stress() noexcept = 0; @@ -261,11 +283,6 @@ class ParticleBase { virtual void compute_updated_position( double dt, bool velocity_update = false) noexcept = 0; - //! Return a state variable - virtual double state_variable( - const std::string& var, - unsigned phase = mpm::ParticlePhase::Solid) const = 0; - //! Return scalar data of particles //! \param[in] property Property string //! \retval data Scalar data of particle property @@ -290,6 +307,22 @@ class ParticleBase { //! Assign material id of this particle to nodes virtual void append_material_id_to_nodes() const = 0; + //! Assign particle free surface + virtual void assign_free_surface(bool free_surface) = 0; + + //! Assign particle free surface + virtual bool free_surface() const = 0; + + //! Compute free surface in particle level by density ratio comparison + virtual bool compute_free_surface_by_density( + double density_ratio_tolerance = 0.65) = 0; + + //! Assign normal vector + virtual void assign_normal(const VectorDim& normal) = 0; + + //! Return normal vector + virtual VectorDim normal() const = 0; + //! Return the number of neighbour particles virtual unsigned nneighbours() const = 0; @@ -315,6 +348,160 @@ class ParticleBase { const std::vector& buffer, std::vector>>& materials) = 0; + //! Navier-Stokes functions---------------------------------- + //! Assigning beta parameter to particle + //! \param[in] pressure parameter determining type of projection + virtual void assign_projection_parameter(double parameter) { + throw std::runtime_error( + "Calling the base class function (assign_projection_parameter) in " + "ParticleBase:: illegal operation!"); + }; + + //! Map laplacian element matrix to cell (used in poisson equation LHS) + virtual bool map_laplacian_to_cell() { + throw std::runtime_error( + "Calling the base class function (map_laplacian_to_cell) in " + "ParticleBase:: " + "illegal operation!"); + return 0; + }; + + //! Map poisson rhs element matrix to cell (used in poisson equation RHS) + virtual bool map_poisson_right_to_cell() { + throw std::runtime_error( + "Calling the base class function (map_poisson_right_to_cell) in " + "ParticleBase:: " + "illegal operation!"); + return 0; + }; + + //! Map correction matrix element matrix to cell (used to correct velocity) + virtual bool map_correction_matrix_to_cell() { + throw std::runtime_error( + "Calling the base class function (map_correction_matrix_to_cell) in " + "ParticleBase:: " + "illegal operation!"); + return 0; + }; + + //! Update pressure after solving poisson equation + virtual bool compute_updated_pressure() { + throw std::runtime_error( + "Calling the base class function (compute_updated_pressure) in " + "ParticleBase:: illegal operation!"); + return 0; + }; + + //! TwoPhase functions-------------------------------------------------------- + //! Update porosity + //! \param[in] dt Analysis time step + virtual void update_porosity(double dt) { + throw std::runtime_error( + "Calling the base class function (update_porosity) in " + "ParticleBase:: illegal operation!"); + }; + + //! Assign saturation degree + virtual bool assign_saturation_degree() { + throw std::runtime_error( + "Calling the base class function (assign_saturation_degree) in " + "ParticleBase:: illegal operation!"); + return 0; + }; + + //! Assign velocity to the particle liquid phase + //! \param[in] velocity A vector of particle liquid phase velocity + //! \retval status Assignment status + virtual bool assign_liquid_velocity(const VectorDim& velocity) { + throw std::runtime_error( + "Calling the base class function (assign_liquid_velocity) in " + "ParticleBase:: illegal operation!"); + return 0; + }; + + //! Compute pore pressure + //! \param[in] dt Time step size + virtual void compute_pore_pressure(double dt) { + throw std::runtime_error( + "Calling the base class function (compute_pore_pressure) in " + "ParticleBase:: illegal operation!"); + }; + + //! Map drag force coefficient + virtual bool map_drag_force_coefficient() { + throw std::runtime_error( + "Calling the base class function (map_drag_force_coefficient) in " + "ParticleBase:: illegal operation!"); + return 0; + }; + + //! Initialise particle pore pressure by watertable + virtual bool initialise_pore_pressure_watertable( + const unsigned dir_v, const unsigned dir_h, const VectorDim& gravity, + std::map& reference_points) { + throw std::runtime_error( + "Calling the base class function " + "(initial_pore_pressure_watertable) in " + "ParticleBase:: illegal operation!"); + return false; + }; + + //! Initialise particle pore pressure by watertable + virtual bool assign_porosity() { + throw std::runtime_error( + "Calling the base class function " + "(assign_porosity) in " + "ParticleBase:: illegal operation!"); + return false; + }; + + //! Initialise particle pore pressure by watertable + virtual bool assign_permeability() { + throw std::runtime_error( + "Calling the base class function " + "(assign_permeability) in " + "ParticleBase:: illegal operation!"); + return false; + }; + + //! Return liquid mass + //! \retval liquid mass Liquid phase mass + virtual double liquid_mass() const { + throw std::runtime_error( + "Calling the base class function (liquid_mass) in " + "ParticleBase:: illegal operation!"); + return 0; + }; + + //! Return velocity of the particle liquid phase + //! \retval liquid velocity Liquid phase velocity + virtual VectorDim liquid_velocity() const { + auto error = VectorDim::Zero(); + throw std::runtime_error( + "Calling the base class function (liquid_velocity) in " + "ParticleBase:: illegal operation!"); + return error; + }; + + //! Return porosity + //! \retval porosity Porosity + virtual double porosity() const { + throw std::runtime_error( + "Calling the base class function (porosity) in " + "ParticleBase:: illegal operation!"); + return 0; + }; + + //! TwoPhase functions specific for semi-implicit + //! Map drag matrix to cell assuming linear-darcy drag force + virtual bool map_drag_matrix_to_cell() { + throw std::runtime_error( + "Calling the base class function (map_drag_matrix_to_cell) in " + "ParticleBase:: illegal operation!"); + return 0; + }; + //---------------------------------------------------------------------------- + protected: //! particleBase id Index id_{std::numeric_limits::max()}; diff --git a/include/particles/particle_fluid.h b/include/particles/particle_fluid.h new file mode 100644 index 000000000..d899b8263 --- /dev/null +++ b/include/particles/particle_fluid.h @@ -0,0 +1,109 @@ +#ifndef MPM_FLUID_PARTICLE_H_ +#define MPM_FLUID_PARTICLE_H_ + +#include +#include +#include +#include +#include + +#include "logger.h" +#include "particle.h" + +namespace mpm { + +//! Fluid Particle class +//! \brief Class with function specific to fluid particles +//! \tparam Tdim Dimension +template +class FluidParticle : public mpm::Particle { + public: + //! Define a vector of size dimension + using VectorDim = Eigen::Matrix; + + //! Construct a particle with id and coordinates + //! \param[in] id Particle id + //! \param[in] coord Coordinates of the particles + FluidParticle(Index id, const VectorDim& coord); + + //! Destructor + ~FluidParticle() override{}; + + //! Delete copy constructor + FluidParticle(const FluidParticle&) = delete; + + //! Delete assignment operator + FluidParticle& operator=(const FluidParticle&) = delete; + + //! Compute stress + void compute_stress() noexcept override; + + //! Map internal force + inline void map_internal_force() noexcept override; + + //! ---------------------------------------------------------------- + //! Semi-Implicit integration functions based on Chorin's Projection + //! ---------------------------------------------------------------- + + //! Assigning beta parameter to particle + //! \param[in] pressure parameter determining type of projection + void assign_projection_parameter(double parameter) override { + this->projection_param_ = parameter; + }; + + //! Map laplacian element matrix to cell (used in poisson equation LHS) + bool map_laplacian_to_cell() override; + + //! Map poisson rhs element matrix to cell (used in poisson equation RHS) + bool map_poisson_right_to_cell() override; + + //! Map correction matrix element matrix to cell (used to correct velocity) + bool map_correction_matrix_to_cell() override; + + //! Update pressure after solving poisson equation + bool compute_updated_pressure() override; + + //! Type of particle + std::string type() const override { + return (Tdim == 2) ? "P2DFLUID" : "P3DFLUID"; + } + + private: + //! Compute turbulent stress + virtual Eigen::Matrix compute_turbulent_stress(); + + private: + //! Cell + using ParticleBase::cell_; + //! Nodes + using ParticleBase::nodes_; + //! Fluid material + using ParticleBase::material_; + //! State variables + using ParticleBase::state_variables_; + //! Shape functions + using Particle::shapefn_; + //! dN/dX + using Particle::dn_dx_; + //! Fluid strain rate + using Particle::strain_rate_; + //! Effective stress of soil skeleton + using Particle::stress_; + //! Particle mass density + using Particle::mass_density_; + //! Particle mass density + using Particle::mass_; + //! Particle total volume + using Particle::volume_; + //! Projection parameter for semi-implicit update + double projection_param_{0.0}; + //! Pressure constraint + double pressure_constraint_{std::numeric_limits::max()}; + //! Logger + std::unique_ptr console_; +}; // FluidParticle class +} // namespace mpm + +#include "particle_fluid.tcc" + +#endif // MPM_FLUID_PARTICLE_H__ diff --git a/include/particles/particle_fluid.tcc b/include/particles/particle_fluid.tcc new file mode 100644 index 000000000..65050f5ed --- /dev/null +++ b/include/particles/particle_fluid.tcc @@ -0,0 +1,206 @@ +//! Construct a two phase particle with id and coordinates +template +mpm::FluidParticle::FluidParticle(Index id, const VectorDim& coord) + : mpm::Particle(id, coord) { + + // Logger + std::string logger = + "FluidParticle" + std::to_string(Tdim) + "d::" + std::to_string(id); + console_ = std::make_unique(logger, mpm::stdout_sink); +} + +// Compute stress +template +void mpm::FluidParticle::compute_stress() noexcept { + // Run particle compute stress + mpm::Particle::compute_stress(); + + // Calculate fluid turbulent stress + this->stress_ += this->compute_turbulent_stress(); +} + +// Compute turbulent stress +template +Eigen::Matrix + mpm::FluidParticle::compute_turbulent_stress() { + // Compute turbulent stress depends on the model + Eigen::Matrix tstress; + tstress.setZero(); + + // Apply LES Smagorinsky closure + const double smagorinsky_constant = 0.2; + const double grid_spacing = std::pow(cell_->volume(), 1 / (double)Tdim); + const auto strain_rate = this->strain_rate(); + double local_strain_rate = 0.0; + local_strain_rate += + 2 * (strain_rate[0] * strain_rate[0] + strain_rate[1] * strain_rate[1] + + strain_rate[2] * strain_rate[2]) + + strain_rate[3] * strain_rate[3] + strain_rate[4] * strain_rate[4] + + strain_rate[5] * strain_rate[5]; + local_strain_rate = std::sqrt(local_strain_rate); + + // Turbulent Eddy Viscosity + const double turbulent_viscosity = + std::pow(smagorinsky_constant * grid_spacing, 2) * local_strain_rate; + + // Turbulent stress + tstress(0) = 2. * turbulent_viscosity / mass_density_ * strain_rate(0); + tstress(1) = 2. * turbulent_viscosity / mass_density_ * strain_rate(1); + tstress(2) = 2. * turbulent_viscosity / mass_density_ * strain_rate(2); + tstress(3) = turbulent_viscosity / mass_density_ * strain_rate(3); + tstress(4) = turbulent_viscosity / mass_density_ * strain_rate(4); + tstress(5) = turbulent_viscosity / mass_density_ * strain_rate(5); + + return tstress; +} + +//! Map internal force +template <> +inline void mpm::FluidParticle<1>::map_internal_force() noexcept { + // initialise a vector of total stress (deviatoric + turbulent - pressure) + Eigen::Matrix total_stress = this->stress_; + total_stress(0) -= + this->projection_param_ * + this->state_variables(mpm::ParticlePhase::SinglePhase)["pressure"]; + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = dn_dx_(i, 0) * total_stress[0]; + force *= -1 * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::SinglePhase, + force); + } +} + +//! Map internal force +template <> +inline void mpm::FluidParticle<2>::map_internal_force() noexcept { + // initialise a vector of total stress (deviatoric + turbulent - pressure) + Eigen::Matrix total_stress = this->stress_; + total_stress(0) -= + this->projection_param_ * + this->state_variables(mpm::ParticlePhase::SinglePhase)["pressure"]; + total_stress(1) -= + this->projection_param_ * + this->state_variables(mpm::ParticlePhase::SinglePhase)["pressure"]; + + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = dn_dx_(i, 0) * total_stress[0] + dn_dx_(i, 1) * total_stress[3]; + force[1] = dn_dx_(i, 1) * total_stress[1] + dn_dx_(i, 0) * total_stress[3]; + + force *= -1. * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::SinglePhase, + force); + } +} + +//! Map internal force +template <> +inline void mpm::FluidParticle<3>::map_internal_force() noexcept { + // initialise a vector of total stress (deviatoric + turbulent - pressure) + Eigen::Matrix total_stress = this->stress_; + total_stress(0) -= + this->projection_param_ * + this->state_variables(mpm::ParticlePhase::SinglePhase)["pressure"]; + total_stress(1) -= + this->projection_param_ * + this->state_variables(mpm::ParticlePhase::SinglePhase)["pressure"]; + total_stress(2) -= + this->projection_param_ * + this->state_variables(mpm::ParticlePhase::SinglePhase)["pressure"]; + + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = dn_dx_(i, 0) * total_stress[0] + dn_dx_(i, 1) * total_stress[3] + + dn_dx_(i, 2) * total_stress[5]; + + force[1] = dn_dx_(i, 1) * total_stress[1] + dn_dx_(i, 0) * total_stress[3] + + dn_dx_(i, 2) * total_stress[4]; + + force[2] = dn_dx_(i, 2) * total_stress[2] + dn_dx_(i, 1) * total_stress[4] + + dn_dx_(i, 0) * total_stress[5]; + + force *= -1. * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::SinglePhase, + force); + } +} + +//! Map laplacian element matrix to cell (used in poisson equation LHS) +template +bool mpm::FluidParticle::map_laplacian_to_cell() { + bool status = true; + try { + // Compute local matrix of Laplacian + cell_->compute_local_laplacian(dn_dx_, volume_); + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Map poisson rhs element matrix to cell (used in poisson equation RHS) +template +bool mpm::FluidParticle::map_poisson_right_to_cell() { + bool status = true; + try { + // Compute local poisson rhs matrix + cell_->compute_local_poisson_right( + shapefn_, dn_dx_, volume_, + this->material(mpm::ParticlePhase::SinglePhase) + ->template property(std::string("density"))); + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Compute updated pressure of the particle based on nodal pressure +template +bool mpm::FluidParticle::compute_updated_pressure() { + bool status = true; + try { + double pressure_increment = 0; + for (unsigned i = 0; i < nodes_.size(); ++i) { + pressure_increment += shapefn_(i) * nodes_[i]->pressure_increment(); + } + + // Get interpolated nodal pressure + state_variables_[mpm::ParticlePhase::SinglePhase].at("pressure") = + state_variables_[mpm::ParticlePhase::SinglePhase].at("pressure") * + projection_param_ + + pressure_increment; + + // Overwrite pressure if free surface + if (this->free_surface()) + state_variables_[mpm::ParticlePhase::SinglePhase].at("pressure") = 0.0; + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Map correction matrix element matrix to cell (used to correct velocity) +template +bool mpm::FluidParticle::map_correction_matrix_to_cell() { + bool status = true; + try { + cell_->compute_local_correction_matrix(shapefn_, dn_dx_, volume_); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return status; +} \ No newline at end of file diff --git a/include/particles/particle_twophase.h b/include/particles/particle_twophase.h new file mode 100644 index 000000000..d39efa98e --- /dev/null +++ b/include/particles/particle_twophase.h @@ -0,0 +1,314 @@ +#ifndef MPM_PARTICLE_TWOPHASE_H_ +#define MPM_PARTICLE_TWOPHASE_H_ + +#include +#include +#include +#include +#include + +#include "logger.h" +#include "particle.h" + +namespace mpm { + +//! TwoPhaseParticle class +//! \brief Class that stores the information about second-phase (liquid) +//! particles +//! \tparam Tdim Dimension +template +class TwoPhaseParticle : public mpm::Particle { + public: + //! Define a vector of size dimension + using VectorDim = Eigen::Matrix; + + //! Construct a twophase particle with id and coordinates + //! \param[in] id Particle id + //! \param[in] coord Coordinates of the particles + TwoPhaseParticle(Index id, const VectorDim& coord); + + //! Construct a twophase particle with id, coordinates and status + //! \param[in] id Particle id + //! \param[in] coord coordinates of the particle + //! \param[in] status Particle status (active / inactive) + TwoPhaseParticle(Index id, const VectorDim& coord, bool status); + + //! Destructor + ~TwoPhaseParticle() override{}; + + //! Delete copy constructor + TwoPhaseParticle(const TwoPhaseParticle&) = delete; + + //! Delete assignment operator + TwoPhaseParticle& operator=(const TwoPhaseParticle&) = delete; + + //! Initialise particle from POD data + //! \param[in] particle POD data of particle + //! \retval status Status of reading POD particle + bool initialise_particle(PODParticle& particle) override; + + //! Initialise particle POD data and material + //! \param[in] particle POD data of particle + //! \param[in] materials Material associated with the particle arranged in a + //! vector + //! \retval status Status of reading POD particle + bool initialise_particle( + PODParticle& particle, + const std::vector>>& materials) override; + + //! Initialise particle liquid phase on top of the regular solid phase + void initialise() override; + + //! Return particle data as POD + //! \retval particle POD of the particle + std::shared_ptr pod() const override; + + //! Assign saturation degree + bool assign_saturation_degree() override; + + //! Compute both solid and liquid mass + void compute_mass() noexcept override; + + //! Map particle mass and momentum to nodes (both solid and liquid) + void map_mass_momentum_to_nodes() noexcept override; + + //! Map body force + //! \param[in] pgravity Gravity of a particle + void map_body_force(const VectorDim& pgravity) noexcept override; + + //! Map traction force + void map_traction_force() noexcept override; + + //! Map internal force + inline void map_internal_force() noexcept override; + + //! Compute updated position of the particle and kinematics of both solid and + //! liquid phase \param[in] dt Analysis time step \param[in] velocity_update + //! Update particle velocity from nodal vel when true + void compute_updated_position(double dt, + bool velocity_update = false) noexcept override; + + //! Assign velocity to the particle liquid phase + //! \param[in] velocity A vector of particle liquid phase velocity + //! \retval status Assignment status + bool assign_liquid_velocity(const VectorDim& velocity) override; + + //! Compute pore pressure + //! \param[in] dt Time step size + void compute_pore_pressure(double dt) noexcept override; + + //! Map drag force coefficient + bool map_drag_force_coefficient() override; + + //! Assign particles initial pore pressure by watertable + //! \param[in] dir_v Vertical direction (Gravity direction) of the watertable + //! \param[in] dir_h Horizontal direction of the watertable + //! \param[in] gravity Gravity vector + //! \param[in] reference_points + //! (Horizontal coordinate of borehole + height of 0 pore pressure) + bool initialise_pore_pressure_watertable( + const unsigned dir_v, const unsigned dir_h, const VectorDim& gravity, + std::map& reference_points); + + //! Update porosity + //! \param[in] dt Analysis time step + void update_porosity(double dt) override; + + //! Assign particle permeability + //! \retval status Assignment status + bool assign_permeability() override; + + //! Assign porosity + bool assign_porosity() override; + + //! Map particle pressure to nodes + bool map_pressure_to_nodes( + unsigned phase = mpm::ParticlePhase::Solid) noexcept override; + + //! Apply particle velocity constraints + //! \param[in] dir Direction of particle velocity constraint + //! \param[in] velocity Applied particle velocity constraint + void apply_particle_velocity_constraints(unsigned dir, + double velocity) override; + + //! Assign traction to the particle + //! \param[in] direction Index corresponding to the direction of traction + //! \param[in] traction Particle traction in specified direction + //! \retval status Assignment status + bool assign_traction(unsigned direction, double traction) override; + + //! Return velocity of the particle liquid phase + //! \retval liquid velocity Liquid phase velocity + VectorDim liquid_velocity() const override { return liquid_velocity_; } + + //! Return liquid mass + //! \retval liquid mass Liquid phase mass + double liquid_mass() const override { return liquid_mass_; } + + //! Reture porosity + //! \retval porosity Porosity + double porosity() const override { return porosity_; } + + //! Type of particle + std::string type() const override { + return (Tdim == 2) ? "P2D2PHASE" : "P3D2PHASE"; + } + + //! Serialize + //! \retval buffer Serialized buffer data + std::vector serialize() override; + + //! Deserialize + //! \param[in] buffer Serialized buffer data + //! \param[in] material Particle material pointers + void deserialize( + const std::vector& buffer, + std::vector>>& materials) override; + + //! ---------------------------------------------------------------- + //! Semi-Implicit integration functions based on Chorin's Projection + //! ---------------------------------------------------------------- + + //! Assigning beta parameter to particle + //! \param[in] pressure parameter determining type of projection + void assign_projection_parameter(double parameter) override { + this->projection_param_ = parameter; + }; + + //! Map drag matrix to cell assuming linear-darcy drag force + bool map_drag_matrix_to_cell() override; + + //! Update pressure after solving poisson equation + bool compute_updated_pressure() override; + + //! Map laplacian element matrix to cell (used in poisson equation LHS) + bool map_laplacian_to_cell() override; + + //! Map poisson rhs element matrix to cell (used in poisson equation RHS) + bool map_poisson_right_to_cell() override; + + //! Map correction matrix element matrix to cell (used to correct velocity) + bool map_correction_matrix_to_cell() override; + + protected: + //! Compute pack size + //! \retval pack size of serialized object + int compute_pack_size() const override; + + private: + //! Assign liquid mass and momentum to nodes + virtual void map_liquid_mass_momentum_to_nodes() noexcept; + + //! Map two phase mixture body force + //! \param[in] mixture Identification for Mixture + //! \param[in] pgravity Gravity of the particle + virtual void map_mixture_body_force(unsigned mixture, + const VectorDim& pgravity) noexcept; + + //! Map liquid body force + //! \param[in] pgravity Gravity of a particle + virtual void map_liquid_body_force(const VectorDim& pgravity) noexcept; + + //! Map two phase mixture traction force + virtual void map_mixture_traction_force() noexcept; + + //! Map two phase liquid traction force + virtual void map_liquid_traction_force() noexcept; + + //! Map liquid internal force + virtual void map_liquid_internal_force() noexcept; + + //! Map two phase mixture internal force + virtual void map_mixture_internal_force() noexcept; + + //! Compute updated velocity of the particle based on nodal velocity + //! \param[in] dt Analysis time step + //! \retval status Compute status + virtual void compute_updated_liquid_velocity(double dt, + bool velocity_update) noexcept; + + protected: + //! particle id + using ParticleBase::id_; + //! coordinates + using ParticleBase::coordinates_; + //! Status + using ParticleBase::status_; + //! Cell + using ParticleBase::cell_; + //! Cell id + using ParticleBase::cell_id_; + //! Nodes + using ParticleBase::nodes_; + //! State variables + using ParticleBase::state_variables_; + //! Shape functions + using Particle::shapefn_; + //! dN/dX + using Particle::dn_dx_; + //! dN/dX at cell centroid + using Particle::dn_dx_centroid_; + //! Size of particle in natural coordinates + using Particle::natural_size_; + //! Materials + using Particle::material_; + //! Material ids + using ParticleBase::material_id_; + //! Particle mass for solid phase + using Particle::mass_; + //! Particle total volume + using Particle::volume_; + //! Particle mass density + using Particle::mass_density_; + //! Displacement + using Particle::displacement_; + //! Velocity + using Particle::velocity_; + //! Effective stress of soil skeleton + using Particle::stress_; + //! Solid skeleton strains + using Particle::strain_; + //! Volumetric strain at centroid + using Particle::volumetric_strain_centroid_; + //! Soil skeleton strain rate + using Particle::strain_rate_; + //! Set traction + using Particle::set_traction_; + //! Surface Traction (given as a stress; force/area) + using Particle::traction_; + //! Size of particle + using Particle::size_; + //! Particle velocity constraints + using Particle::particle_velocity_constraints_; + //! Size of particle + using Particle::pack_size_; + + //! Liquid mass + double liquid_mass_; + //! Liquid mass density (bulk density = liquid mass / total volume) + double liquid_mass_density_; + //! Degree of saturation + double liquid_saturation_{1.0}; + //! Material point porosity (volume of voids / total volume) + double porosity_{0.0}; + //! Set liquid traction + bool set_liquid_traction_{false}; + //! Liquid traction + Eigen::Matrix liquid_traction_; + //! Liquid velocity + Eigen::Matrix liquid_velocity_; + //! Projection parameter for semi-implicit update + double projection_param_{1.0}; + //! Pore pressure constraint + double pore_pressure_constraint_{std::numeric_limits::max()}; + //! Permeability parameter c1 (k = k_p * c1) + VectorDim permeability_; + //! Logger + std::unique_ptr console_; + +}; // TwoPhaseParticle class +} // namespace mpm + +#include "particle_twophase.tcc" + +#endif // MPM_PARTICLE_TWOPHASE_H__ diff --git a/include/particles/particle_twophase.tcc b/include/particles/particle_twophase.tcc new file mode 100644 index 000000000..d7d153dfc --- /dev/null +++ b/include/particles/particle_twophase.tcc @@ -0,0 +1,1363 @@ +//! Construct a two phase particle with id and coordinates +template +mpm::TwoPhaseParticle::TwoPhaseParticle(Index id, const VectorDim& coord) + : mpm::Particle(id, coord) { + this->initialise(); + // Clear cell ptr + cell_ = nullptr; + // Nodes + nodes_.clear(); + // Set material containers + this->initialise_material(2); + // Logger + std::string logger = + "twophaseparticle" + std::to_string(Tdim) + "d::" + std::to_string(id); + console_ = std::make_unique(logger, mpm::stdout_sink); +} + +//! Construct a twophase particle with id, coordinates and status +template +mpm::TwoPhaseParticle::TwoPhaseParticle(Index id, const VectorDim& coord, + bool status) + : mpm::Particle(id, coord, status) { + this->initialise(); + cell_ = nullptr; + nodes_.clear(); + // Set material containers + this->initialise_material(2); + //! Logger + std::string logger = + "twophaseparticle" + std::to_string(Tdim) + "d::" + std::to_string(id); + console_ = std::make_unique(logger, mpm::stdout_sink); +} + +//! Return particle data as POD +template +// cppcheck-suppress * +std::shared_ptr mpm::TwoPhaseParticle::pod() const { + // Initialise particle_data + auto particle_data = std::make_shared(); + + Eigen::Vector3d coordinates; + coordinates.setZero(); + for (unsigned j = 0; j < Tdim; ++j) coordinates[j] = this->coordinates_[j]; + + Eigen::Vector3d displacement; + displacement.setZero(); + for (unsigned j = 0; j < Tdim; ++j) displacement[j] = this->displacement_[j]; + + Eigen::Vector3d velocity; + velocity.setZero(); + for (unsigned j = 0; j < Tdim; ++j) velocity[j] = this->velocity_[j]; + + // Particle local size + Eigen::Vector3d nsize; + nsize.setZero(); + Eigen::VectorXd size = this->natural_size(); + for (unsigned j = 0; j < Tdim; ++j) nsize[j] = size[j]; + + Eigen::Matrix stress = this->stress_; + + Eigen::Matrix strain = this->strain_; + + particle_data->id = this->id(); + particle_data->mass = this->mass(); + particle_data->volume = this->volume(); + particle_data->pressure = + (state_variables_[mpm::ParticlePhase::Solid].find("pressure") != + state_variables_[mpm::ParticlePhase::Solid].end()) + ? state_variables_[mpm::ParticlePhase::Solid].at("pressure") + : 0.; + + particle_data->coord_x = coordinates[0]; + particle_data->coord_y = coordinates[1]; + particle_data->coord_z = coordinates[2]; + + particle_data->displacement_x = displacement[0]; + particle_data->displacement_y = displacement[1]; + particle_data->displacement_z = displacement[2]; + + particle_data->nsize_x = nsize[0]; + particle_data->nsize_y = nsize[1]; + particle_data->nsize_z = nsize[2]; + + particle_data->velocity_x = velocity[0]; + particle_data->velocity_y = velocity[1]; + particle_data->velocity_z = velocity[2]; + + particle_data->stress_xx = stress[0]; + particle_data->stress_yy = stress[1]; + particle_data->stress_zz = stress[2]; + particle_data->tau_xy = stress[3]; + particle_data->tau_yz = stress[4]; + particle_data->tau_xz = stress[5]; + + particle_data->strain_xx = strain[0]; + particle_data->strain_yy = strain[1]; + particle_data->strain_zz = strain[2]; + particle_data->gamma_xy = strain[3]; + particle_data->gamma_yz = strain[4]; + particle_data->gamma_xz = strain[5]; + + particle_data->epsilon_v = this->volumetric_strain_centroid_; + + particle_data->status = this->status(); + + particle_data->cell_id = this->cell_id(); + + particle_data->material_id = this->material_id(); + + // Write state variables + if (this->material() != nullptr) { + particle_data->nstate_vars = + state_variables_[mpm::ParticlePhase::Solid].size(); + if (state_variables_[mpm::ParticlePhase::Solid].size() > 20) + throw std::runtime_error("# of state variables cannot be more than 20"); + unsigned i = 0; + auto state_variables = (this->material())->state_variables(); + for (const auto& state_var : state_variables) { + particle_data->svars[i] = + state_variables_[mpm::ParticlePhase::Solid].at(state_var); + ++i; + } + } + + // Particle liquid mass + particle_data->liquid_mass = this->liquid_mass_; + + // Particle liquid velocity + Eigen::Vector3d liquid_velocity; + liquid_velocity.setZero(); + for (unsigned j = 0; j < Tdim; ++j) + liquid_velocity[j] = this->liquid_velocity_[j]; + + particle_data->liquid_velocity_x = liquid_velocity[0]; + particle_data->liquid_velocity_y = liquid_velocity[1]; + particle_data->liquid_velocity_z = liquid_velocity[2]; + + // Particle porosity and saturation + particle_data->porosity = this->porosity_; + particle_data->liquid_saturation = this->liquid_saturation_; + + // Particle liquid material id + particle_data->liquid_material_id = + this->material_id(mpm::ParticlePhase::Liquid); + + // Write state variables + if (this->material(mpm::ParticlePhase::Liquid) != nullptr) { + particle_data->nliquid_state_vars = + state_variables_[mpm::ParticlePhase::Liquid].size(); + if (state_variables_[mpm::ParticlePhase::Liquid].size() > 5) + throw std::runtime_error("# of state variables cannot be more than 5"); + unsigned i = 0; + auto state_variables = + (this->material(mpm::ParticlePhase::Liquid))->state_variables(); + for (const auto& state_var : state_variables) { + particle_data->liquid_svars[i] = + state_variables_[mpm::ParticlePhase::Liquid].at(state_var); + ++i; + } + } + + return particle_data; +} + +//! Initialise particle data from POD +template +bool mpm::TwoPhaseParticle::initialise_particle(PODParticle& particle) { + // Initialise solid phase + bool status = mpm::Particle::initialise_particle(particle); + auto twophase_particle = reinterpret_cast(&particle); + + // Liquid mass + this->liquid_mass_ = twophase_particle->liquid_mass; + // Liquid mass Density + this->liquid_mass_density_ = twophase_particle->liquid_mass / particle.volume; + + // Liquid velocity + Eigen::Vector3d liquid_velocity; + liquid_velocity << twophase_particle->liquid_velocity_x, + twophase_particle->liquid_velocity_y, + twophase_particle->liquid_velocity_z; + // Initialise velocity + for (unsigned i = 0; i < Tdim; ++i) + this->liquid_velocity_(i) = liquid_velocity(i); + + // Particle porosity and saturation + this->porosity_ = twophase_particle->porosity; + this->liquid_saturation_ = twophase_particle->liquid_saturation; + + // Liquid material id + this->material_id_[mpm::ParticlePhase::Liquid] = + twophase_particle->liquid_material_id; + + return status; +} + +//! Initialise particle data from POD +template +bool mpm::TwoPhaseParticle::initialise_particle( + PODParticle& particle, + const std::vector>>& materials) { + auto twophase_particle = reinterpret_cast(&particle); + bool status = this->initialise_particle(*twophase_particle); + + assert(materials.size() == 2); + + // Solid Phase + const auto& solid_material = materials.at(mpm::ParticlePhase::Solid); + if (solid_material != nullptr) { + if (this->material_id(mpm::ParticlePhase::Solid) == solid_material->id() || + this->material_id(mpm::ParticlePhase::Solid) == + std::numeric_limits::max()) { + bool assign_mat = + this->assign_material(solid_material, mpm::ParticlePhase::Solid); + if (!assign_mat) throw std::runtime_error("Material assignment failed"); + // Reinitialize state variables + auto mat_state_vars = (this->material(mpm::ParticlePhase::Solid)) + ->initialise_state_variables(); + if (mat_state_vars.size() == twophase_particle->nstate_vars) { + unsigned i = 0; + auto state_variables = + (this->material(mpm::ParticlePhase::Solid))->state_variables(); + for (const auto& state_var : state_variables) { + this->state_variables_[mpm::ParticlePhase::Solid].at(state_var) = + twophase_particle->svars[i]; + ++i; + } + } + } else { + status = false; + throw std::runtime_error("Material is invalid to assign to particle!"); + } + } + + // Fluid Phase + const auto& liquid_material = materials.at(mpm::ParticlePhase::Liquid); + if (liquid_material != nullptr) { + if (this->material_id(mpm::ParticlePhase::Liquid) == + liquid_material->id() || + this->material_id(mpm::ParticlePhase::Liquid) == + std::numeric_limits::max()) { + bool assign_mat = + this->assign_material(liquid_material, mpm::ParticlePhase::Liquid); + if (!assign_mat) throw std::runtime_error("Material assignment failed"); + // Reinitialize state variables + auto mat_state_vars = (this->material(mpm::ParticlePhase::Liquid)) + ->initialise_state_variables(); + if (mat_state_vars.size() == twophase_particle->nliquid_state_vars) { + unsigned i = 0; + auto state_variables = + (this->material(mpm::ParticlePhase::Liquid))->state_variables(); + for (const auto& state_var : state_variables) { + this->state_variables_[mpm::ParticlePhase::Liquid].at(state_var) = + twophase_particle->liquid_svars[i]; + ++i; + } + } + } else { + status = false; + throw std::runtime_error("Material is invalid to assign to particle!"); + } + } + + // Assign permeability + this->assign_permeability(); + + return status; +} + +// Initialise liquid phase particle properties +template +void mpm::TwoPhaseParticle::initialise() { + mpm::Particle::initialise(); + liquid_mass_ = 0.; + liquid_velocity_.setZero(); + set_liquid_traction_ = false; + permeability_.setZero(); + liquid_traction_.setZero(); + liquid_saturation_ = 1.; + + // Initialize vector data properties + this->vector_properties_["liquid_velocities"] = [&]() { + return liquid_velocity(); + }; +} + +// Assign degree of saturation to the liquid phase +template +bool mpm::TwoPhaseParticle::assign_saturation_degree() { + bool status = true; + try { + if (this->material(mpm::ParticlePhase::Liquid) != nullptr) { + liquid_saturation_ = + this->material(mpm::ParticlePhase::Liquid) + ->template property(std::string("saturation")); + if (liquid_saturation_ < 0. || liquid_saturation_ > 1.) + throw std::runtime_error( + "Particle saturation degree is negative or larger than one"); + } else { + throw std::runtime_error("Liquid material is invalid"); + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Assign velocity to the particle liquid phase +template +bool mpm::TwoPhaseParticle::assign_liquid_velocity( + const Eigen::Matrix& velocity) { + bool status = false; + try { + // Assign velocity + liquid_velocity_ = velocity; + status = true; + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Compute mass of particle (both solid and fluid) +template +void mpm::TwoPhaseParticle::compute_mass() noexcept { + // Check if particle volume is set and material ptr is valid + assert(volume_ != std::numeric_limits::max() && + this->material(mpm::ParticlePhase::Solid) != nullptr && + this->material(mpm::ParticlePhase::Liquid) != nullptr); + // Mass = volume of particle * mass_density + // Solid mass + this->mass_density_ = + (this->material(mpm::ParticlePhase::Solid)) + ->template property(std::string("density")) * + (1 - this->porosity_); + this->mass_ = volume_ * mass_density_; + + // Liquid mass + this->liquid_mass_density_ = + liquid_saturation_ * porosity_ * + (this->material(mpm::ParticlePhase::Liquid)) + ->template property(std::string("density")); + this->liquid_mass_ = volume_ * liquid_mass_density_; +} + +//! Map particle mass and momentum to nodes +template +void mpm::TwoPhaseParticle::map_mass_momentum_to_nodes() noexcept { + mpm::Particle::map_mass_momentum_to_nodes(); + this->map_liquid_mass_momentum_to_nodes(); +} + +//! Map liquid mass and momentum to nodes +template +void mpm::TwoPhaseParticle::map_liquid_mass_momentum_to_nodes() noexcept { + // Check if liquid mass is set and positive + assert(liquid_mass_ != std::numeric_limits::max()); + + // Map liquid mass and momentum to nodes + for (unsigned i = 0; i < nodes_.size(); ++i) { + nodes_[i]->update_mass(true, mpm::ParticlePhase::Liquid, + liquid_mass_ * shapefn_[i]); + nodes_[i]->update_momentum(true, mpm::ParticlePhase::Liquid, + liquid_mass_ * shapefn_[i] * liquid_velocity_); + } +} + +//! Compute pore pressure +template +void mpm::TwoPhaseParticle::compute_pore_pressure(double dt) noexcept { + // Check if liquid material and cell pointer are set and positive + assert(this->material(mpm::ParticlePhase::Liquid) != nullptr && + cell_ != nullptr); + + // get the bulk modulus of liquid + double K = this->material(mpm::ParticlePhase::Liquid) + ->template property(std::string("bulk_modulus")); + + // Compute at centroid + // get phase-wise strain rate at cell centre + auto strain_rate_centroid = + this->compute_strain_rate(dn_dx_centroid_, mpm::ParticlePhase::Solid); + + auto liquid_strain_rate_centroid = + this->compute_strain_rate(dn_dx_centroid_, mpm::ParticlePhase::Liquid); + + // update pressure + this->state_variables_[mpm::ParticlePhase::Liquid].at("pressure") += + -dt * (K / porosity_) * + ((1 - porosity_) * strain_rate_centroid.head(Tdim).sum() + + porosity_ * liquid_strain_rate_centroid.head(Tdim).sum()); + + // Apply free surface + if (this->free_surface()) + this->assign_state_variable("pressure", 0., mpm::ParticlePhase::Liquid); +} + +//! Map body force for both mixture and liquid +template +void mpm::TwoPhaseParticle::map_body_force( + const VectorDim& pgravity) noexcept { + this->map_mixture_body_force(mpm::ParticlePhase::Mixture, pgravity); + this->map_liquid_body_force(pgravity); +} + +//! Map liquid phase body force +template +void mpm::TwoPhaseParticle::map_liquid_body_force( + const VectorDim& pgravity) noexcept { + // Compute nodal liquid body forces + for (unsigned i = 0; i < nodes_.size(); ++i) + nodes_[i]->update_external_force( + true, mpm::ParticlePhase::Liquid, + (pgravity * this->liquid_mass_ * shapefn_(i))); +} + +//! Map mixture body force +template +void mpm::TwoPhaseParticle::map_mixture_body_force( + unsigned mixture, const VectorDim& pgravity) noexcept { + // Compute nodal mixture body forces + for (unsigned i = 0; i < nodes_.size(); ++i) + nodes_[i]->update_external_force( + true, mixture, + (pgravity * (this->liquid_mass_ + this->mass_) * shapefn_(i))); +} + +//! Map traction force +template +void mpm::TwoPhaseParticle::map_traction_force() noexcept { + if (this->set_traction_) this->map_mixture_traction_force(); + if (this->set_liquid_traction_) this->map_liquid_traction_force(); +} + +//! Map mixture traction force +template +void mpm::TwoPhaseParticle::map_mixture_traction_force() noexcept { + // Map particle traction forces to nodes + for (unsigned i = 0; i < nodes_.size(); ++i) + nodes_[i]->update_external_force(true, mpm::ParticlePhase::Mixture, + (shapefn_[i] * traction_)); +} + +//! Map liquid traction force +template +void mpm::TwoPhaseParticle::map_liquid_traction_force() noexcept { + // Map particle liquid traction forces to nodes + for (unsigned i = 0; i < nodes_.size(); ++i) + nodes_[i]->update_external_force(true, mpm::ParticlePhase::Liquid, + (shapefn_[i] * liquid_traction_)); +} + +//! Map both mixture and liquid internal force +template +inline void mpm::TwoPhaseParticle::map_internal_force() noexcept { + mpm::TwoPhaseParticle::map_mixture_internal_force(); + mpm::TwoPhaseParticle::map_liquid_internal_force(); +} + +//! Map liquid phase internal force +template <> +inline void mpm::TwoPhaseParticle<1>::map_liquid_internal_force() noexcept { + // pore pressure + const double pressure = + -this->state_variable("pressure", mpm::ParticlePhase::Liquid); + + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = + dn_dx_(i, 0) * pressure * this->porosity_ * this->projection_param_; + + force *= -1. * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::Liquid, force); + } +} + +//! Map liquid phase internal force +template <> +inline void mpm::TwoPhaseParticle<2>::map_liquid_internal_force() noexcept { + // pore pressure + const double pressure = + -this->state_variable("pressure", mpm::ParticlePhase::Liquid); + + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = + dn_dx_(i, 0) * pressure * this->porosity_ * this->projection_param_; + force[1] = + dn_dx_(i, 1) * pressure * this->porosity_ * this->projection_param_; + + force *= -1. * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::Liquid, force); + } +} + +template <> +inline void mpm::TwoPhaseParticle<3>::map_liquid_internal_force() noexcept { + // pore pressure + const double pressure = + -this->state_variable("pressure", mpm::ParticlePhase::Liquid); + + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = + dn_dx_(i, 0) * pressure * this->porosity_ * this->projection_param_; + force[1] = + dn_dx_(i, 1) * pressure * this->porosity_ * this->projection_param_; + force[2] = + dn_dx_(i, 2) * pressure * this->porosity_ * this->projection_param_; + + force *= -1. * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::Liquid, force); + } +} + +//! Map mixture internal force +template <> +inline void mpm::TwoPhaseParticle<1>::map_mixture_internal_force() noexcept { + // pore pressure + const double pressure = + -this->state_variable("pressure", mpm::ParticlePhase::Liquid); + // total stress + Eigen::Matrix total_stress = this->stress_; + total_stress(0) += pressure * this->projection_param_; + + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = dn_dx_(i, 0) * total_stress[0] + dn_dx_(i, 1) * total_stress[3]; + + force *= -1. * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::Mixture, force); + } +} + +//! Map mixture internal force +template <> +inline void mpm::TwoPhaseParticle<2>::map_mixture_internal_force() noexcept { + // pore pressure + const double pressure = + -this->state_variable("pressure", mpm::ParticlePhase::Liquid); + // total stress + Eigen::Matrix total_stress = this->stress_; + total_stress(0) += pressure * this->projection_param_; + total_stress(1) += pressure * this->projection_param_; + + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = dn_dx_(i, 0) * total_stress[0] + dn_dx_(i, 1) * total_stress[3]; + force[1] = dn_dx_(i, 1) * total_stress[1] + dn_dx_(i, 0) * total_stress[3]; + + force *= -1. * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::Mixture, force); + } +} + +template <> +inline void mpm::TwoPhaseParticle<3>::map_mixture_internal_force() noexcept { + // pore pressure + const double pressure = + -this->state_variable("pressure", mpm::ParticlePhase::Liquid); + // total stress + Eigen::Matrix total_stress = this->stress_; + total_stress(0) += pressure * this->projection_param_; + total_stress(1) += pressure * this->projection_param_; + total_stress(2) += pressure * this->projection_param_; + + // Compute nodal internal forces + for (unsigned i = 0; i < nodes_.size(); ++i) { + // Compute force: -pstress * volume + Eigen::Matrix force; + force[0] = dn_dx_(i, 0) * total_stress[0] + dn_dx_(i, 1) * total_stress[3] + + dn_dx_(i, 2) * total_stress[5]; + + force[1] = dn_dx_(i, 1) * total_stress[1] + dn_dx_(i, 0) * total_stress[3] + + dn_dx_(i, 2) * total_stress[4]; + + force[2] = dn_dx_(i, 2) * total_stress[2] + dn_dx_(i, 1) * total_stress[4] + + dn_dx_(i, 0) * total_stress[5]; + + force *= -1. * this->volume_; + + nodes_[i]->update_internal_force(true, mpm::ParticlePhase::Mixture, force); + } +} + +// Compute updated position of the particle and kinematics of both solid and +// liquid phase +template +void mpm::TwoPhaseParticle::compute_updated_position( + double dt, bool velocity_update) noexcept { + mpm::Particle::compute_updated_position(dt, velocity_update); + this->compute_updated_liquid_velocity(dt, velocity_update); +} + +//! Map particle pressure to nodes +template +bool mpm::TwoPhaseParticle::map_pressure_to_nodes( + unsigned phase) noexcept { + // Mass is initialized + assert(liquid_mass_ != std::numeric_limits::max()); + + bool status = false; + // If phase is Solid, use the default map_pressure_to_nodes + if (phase == mpm::ParticlePhase::Solid) + status = mpm::Particle::map_pressure_to_nodes(phase); + else { + // Check if particle liquid mass is set and state variable pressure is found + if (liquid_mass_ != std::numeric_limits::max() && + (state_variables_[phase].find("pressure") != + state_variables_[phase].end())) { + // Map particle pressure to nodes + for (unsigned i = 0; i < nodes_.size(); ++i) + nodes_[i]->update_mass_pressure( + phase, + shapefn_[i] * liquid_mass_ * state_variables_[phase]["pressure"]); + + status = true; + } + } + return status; +} + +// Compute updated velocity of the liquid phase based on nodal velocity +template +void mpm::TwoPhaseParticle::compute_updated_liquid_velocity( + double dt, bool velocity_update) noexcept { + // Check if particle has a valid cell ptr + assert(cell_ != nullptr); + + if (!velocity_update) { + // Get interpolated nodal acceleration + Eigen::Matrix acceleration; + acceleration.setZero(); + + for (unsigned i = 0; i < nodes_.size(); ++i) + acceleration += + shapefn_(i) * nodes_[i]->acceleration(mpm::ParticlePhase::Liquid); + + // Update particle velocity from interpolated nodal acceleration + this->liquid_velocity_ += acceleration * dt; + } else { + // Get interpolated nodal velocity + Eigen::Matrix velocity; + velocity.setZero(); + + for (unsigned i = 0; i < nodes_.size(); ++i) + velocity += shapefn_(i) * nodes_[i]->velocity(mpm::ParticlePhase::Liquid); + + // Update particle velocity to interpolated nodal velocity + this->liquid_velocity_ = velocity; + } +} + +//! Apply particle velocity constraints +template +void mpm::TwoPhaseParticle::apply_particle_velocity_constraints( + unsigned dir, double velocity) { + // Set particle velocity constraint for solid phase + if (dir < Tdim) + mpm::Particle::apply_particle_velocity_constraints(dir, velocity); + + // Set particle velocity constraint for liquid phase + else + this->liquid_velocity_(static_cast(dir % Tdim)) = velocity; +} + +// Assign porosity to the particle +template +bool mpm::TwoPhaseParticle::assign_porosity() { + bool status = true; + try { + if (this->material(mpm::ParticlePhase::Solid) != nullptr) { + this->porosity_ = + this->material(mpm::ParticlePhase::Solid) + ->template property(std::string("porosity")); + if (porosity_ < 0. || porosity_ > 1.) + throw std::runtime_error( + "Particle porosity is negative or larger than one"); + } else { + throw std::runtime_error("Material is invalid"); + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Assign particle permeability +template +bool mpm::TwoPhaseParticle::assign_permeability() { + bool status = true; + try { + // Check if material ptr is valid + if (this->material(mpm::ParticlePhase::Solid) != nullptr) { + // Get initial porosity + double porosity = + this->material(mpm::ParticlePhase::Solid) + ->template property(std::string("porosity")); + if (porosity < 0. || porosity > 1.) + throw std::runtime_error( + "Particle porosity is negative or larger than one, can not assign " + "permeability"); + // Porosity parameter + const double k_p = std::pow(porosity, 3) / std::pow((1. - porosity), 2); + // Assign permeability + switch (Tdim) { + case (3): + // Check if the permeability is valid + if (this->material(mpm::ParticlePhase::Solid) + ->template property("k_z") < 0) + throw std::runtime_error("Material's permeability is invalid"); + permeability_(2) = this->material(mpm::ParticlePhase::Solid) + ->template property("k_z") / + k_p; + case (2): + // Check if the permeability is valid + if (this->material(mpm::ParticlePhase::Solid) + ->template property("k_y") < 0) + throw std::runtime_error("Material's permeability is invalid"); + permeability_(1) = this->material(mpm::ParticlePhase::Solid) + ->template property("k_y") / + k_p; + case (1): + // Check if the permeability is valid + if (this->material(mpm::ParticlePhase::Solid) + ->template property("k_x") < 0) + throw std::runtime_error("Material's permeability is invalid"); + // Assign permeability + permeability_(0) = this->material(mpm::ParticlePhase::Solid) + ->template property("k_x") / + k_p; + } + } else { + throw std::runtime_error("Material is invalid"); + } + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Map drag force +template +bool mpm::TwoPhaseParticle::map_drag_force_coefficient() { + bool status = true; + try { + // Porosity parameter + const double k_p = + std::pow(this->porosity_, 3) / std::pow((1. - this->porosity_), 2); + // Initialise drag force coefficient + VectorDim drag_force_coefficient; + drag_force_coefficient.setZero(); + + // Check if permeability coefficient is valid + const double liquid_unit_weight = + 9.81 * this->material(mpm::ParticlePhase::Liquid) + ->template property(std::string("density")); + for (unsigned i = 0; i < Tdim; ++i) { + if (k_p > 0.) + drag_force_coefficient(i) = porosity_ * porosity_ * liquid_unit_weight / + (k_p * permeability_(i)); + else + throw std::runtime_error("Porosity coefficient is invalid"); + } + + // Map drag forces from particle to nodes + for (unsigned j = 0; j < nodes_.size(); ++j) + nodes_[j]->update_drag_force_coefficient( + true, drag_force_coefficient * this->volume_ * shapefn_(j)); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Initial pore pressure +template +bool mpm::TwoPhaseParticle::initialise_pore_pressure_watertable( + const unsigned dir_v, const unsigned dir_h, const VectorDim& gravity, + std::map& reference_points) { + bool status = true; + try { + // Initialise left boundary position (coordinate) and h0 + double left_boundary = std::numeric_limits::lowest(); + double h0_left = 0.; + // Initialise right boundary position (coordinate) and h0 + double right_boundary = std::numeric_limits::max(); + double h0_right = 0.; + // Position and h0 of particle (coordinate) + const double position = this->coordinates_(dir_h); + // Iterate over each reference_points + for (const auto& reference_point : reference_points) { + // Find boundary + if (reference_point.first > left_boundary && + reference_point.first <= position) { + // Left boundary position and h0 + left_boundary = reference_point.first; + h0_left = reference_point.second; + } else if (reference_point.first > position && + reference_point.first <= right_boundary) { + // Right boundary position and h0 + right_boundary = reference_point.first; + h0_right = reference_point.second; + } + } + + // Initialise pore pressure + double pore_pressure = 0; + + if (left_boundary != std::numeric_limits::lowest()) { + // Particle with left and right boundary + if (right_boundary != std::numeric_limits::max()) { + pore_pressure = + ((h0_right - h0_left) / (right_boundary - left_boundary) * + (position - left_boundary) + + h0_left - this->coordinates_(dir_v)) * + (this->material(mpm::ParticlePhase::Liquid)) + ->template property(std::string("density")) * + (-gravity(dir_v)); + } else + // Particle with only left boundary + pore_pressure = + (h0_left - this->coordinates_(dir_v)) * + (this->material(mpm::ParticlePhase::Liquid)) + ->template property(std::string("density")) * + (-gravity(dir_v)); + + } + // Particle with only right boundary + else if (right_boundary != std::numeric_limits::max()) + pore_pressure = (h0_right - this->coordinates_(dir_v)) * + (this->material(mpm::ParticlePhase::Liquid)) + ->template property(std::string("density")) * + (-gravity(dir_v)); + else + throw std::runtime_error( + "Particle pore pressure can not be initialised by water table"); + + // Check negative pore pressure + if (pore_pressure < 0) pore_pressure = 0; + + // Assign pore pressure + this->assign_state_variable("pressure", pore_pressure, + mpm::ParticlePhase::Liquid); + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Update material point porosity +template +void mpm::TwoPhaseParticle::update_porosity(double dt) { + // Update particle porosity + const double porosity = + 1 - (1 - this->porosity_) / (1 + dt * strain_rate_.head(Tdim).sum()); + // Check if the value is valid + if (porosity < 0.) + this->porosity_ = 1.E-5; + else if (porosity > 1.) + this->porosity_ = 1 - 1.E-5; + else + this->porosity_ = porosity; +} + +// Assign traction to the particle +template +bool mpm::TwoPhaseParticle::assign_traction(unsigned direction, + double traction) { + bool status = false; + try { + if (direction >= Tdim * 2 || + this->volume_ == std::numeric_limits::max()) { + throw std::runtime_error( + "Particle traction property: volume / direction is invalid"); + } + // Assign mixture traction + if (direction < Tdim) { + this->set_traction_ = true; + traction_(direction) = traction * this->volume_ / this->size_(direction); + } + // Assign liquid traction + else { + this->set_liquid_traction_ = true; + liquid_traction_(direction - Tdim) = + traction * this->volume_ / this->size_(direction - Tdim); + } + status = true; + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Compute size of serialized particle data +template +int mpm::TwoPhaseParticle::compute_pack_size() const { + int total_size = mpm::Particle::compute_pack_size(); + int partial_size; +#ifdef USE_MPI + // material id for liquid phase + MPI_Pack_size(1, MPI_UNSIGNED, MPI_COMM_WORLD, &partial_size); + total_size += partial_size; + + // liquid mass + MPI_Pack_size(1, MPI_DOUBLE, MPI_COMM_WORLD, &partial_size); + total_size += partial_size; + + // liquid velocity + MPI_Pack_size(Tdim, MPI_DOUBLE, MPI_COMM_WORLD, &partial_size); + total_size += partial_size; + + // porosity, liquid saturation + MPI_Pack_size(2 * 1, MPI_DOUBLE, MPI_COMM_WORLD, &partial_size); + total_size += partial_size; + + // nliquid state variables + unsigned nliquid_state_vars = + state_variables_[mpm::ParticlePhase::Liquid].size(); + MPI_Pack_size(1, MPI_UNSIGNED, MPI_COMM_WORLD, &partial_size); + total_size += partial_size; + + // liquid state variables + MPI_Pack_size(nliquid_state_vars, MPI_DOUBLE, MPI_COMM_WORLD, &partial_size); + total_size += partial_size; +#endif + return total_size; +} + +//! Serialize particle data +template +std::vector mpm::TwoPhaseParticle::serialize() { + // Compute pack size + if (pack_size_ == 0) pack_size_ = this->compute_pack_size(); + // Initialize data buffer + std::vector data; + data.resize(pack_size_); + uint8_t* data_ptr = &data[0]; + int position = 0; + +#ifdef USE_MPI + // Type + int type = ParticleType.at(this->type()); + MPI_Pack(&type, 1, MPI_INT, data_ptr, data.size(), &position, MPI_COMM_WORLD); + + // Material ID + unsigned nmaterials = material_id_.size(); + MPI_Pack(&nmaterials, 1, MPI_UNSIGNED, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + MPI_Pack(&material_id_[mpm::ParticlePhase::Solid], 1, MPI_UNSIGNED, data_ptr, + data.size(), &position, MPI_COMM_WORLD); + MPI_Pack(&material_id_[mpm::ParticlePhase::Liquid], 1, MPI_UNSIGNED, data_ptr, + data.size(), &position, MPI_COMM_WORLD); + + // ID + MPI_Pack(&id_, 1, MPI_UNSIGNED_LONG_LONG, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + + // Solid Phase + // Mass + MPI_Pack(&mass_, 1, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + // Volume + MPI_Pack(&volume_, 1, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + // Pressure + double pressure = + (state_variables_[mpm::ParticlePhase::Solid].find("pressure") != + state_variables_[mpm::ParticlePhase::Solid].end()) + ? state_variables_[mpm::ParticlePhase::Solid].at("pressure") + : 0.; + MPI_Pack(&pressure, 1, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + + // Coordinates + MPI_Pack(coordinates_.data(), Tdim, MPI_DOUBLE, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + // Displacement + MPI_Pack(displacement_.data(), Tdim, MPI_DOUBLE, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + // Natural size + MPI_Pack(natural_size_.data(), Tdim, MPI_DOUBLE, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + // Velocity + MPI_Pack(velocity_.data(), Tdim, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + // Stress + MPI_Pack(stress_.data(), 6, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + // Strain + MPI_Pack(strain_.data(), 6, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + + // epsv + MPI_Pack(&volumetric_strain_centroid_, 1, MPI_DOUBLE, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + + // Cell id + MPI_Pack(&cell_id_, 1, MPI_UNSIGNED_LONG_LONG, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + + // Status + MPI_Pack(&status_, 1, MPI_C_BOOL, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + + // nstate variables + unsigned nstate_vars = state_variables_[mpm::ParticlePhase::Solid].size(); + MPI_Pack(&nstate_vars, 1, MPI_UNSIGNED, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + + // state variables + if (this->material(mpm::ParticlePhase::Solid) != nullptr) { + std::vector svars; + auto state_variables = + (this->material(mpm::ParticlePhase::Solid))->state_variables(); + for (const auto& state_var : state_variables) + svars.emplace_back( + state_variables_[mpm::ParticlePhase::Solid].at(state_var)); + + // Write state vars + MPI_Pack(&svars[0], nstate_vars, MPI_DOUBLE, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + } + + // Liquid Phase + // Mass + MPI_Pack(&liquid_mass_, 1, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + // Velocity + MPI_Pack(liquid_velocity_.data(), Tdim, MPI_DOUBLE, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + // Porosity + MPI_Pack(&porosity_, 1, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + // Liquid Saturation + MPI_Pack(&liquid_saturation_, 1, MPI_DOUBLE, data_ptr, data.size(), &position, + MPI_COMM_WORLD); + + // nstate variables + unsigned nliquid_state_vars = + state_variables_[mpm::ParticlePhase::Liquid].size(); + MPI_Pack(&nliquid_state_vars, 1, MPI_UNSIGNED, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + + // state variables + if (this->material(mpm::ParticlePhase::Liquid) != nullptr) { + std::vector svars; + auto state_variables = + (this->material(mpm::ParticlePhase::Liquid))->state_variables(); + for (const auto& state_var : state_variables) + svars.emplace_back( + state_variables_[mpm::ParticlePhase::Liquid].at(state_var)); + + // Write state vars + MPI_Pack(&svars[0], nliquid_state_vars, MPI_DOUBLE, data_ptr, data.size(), + &position, MPI_COMM_WORLD); + } +#endif + return data; +} + +//! Deserialize particle data +template +void mpm::TwoPhaseParticle::deserialize( + const std::vector& data, + std::vector>>& materials) { + uint8_t* data_ptr = const_cast(&data[0]); + int position = 0; + +#ifdef USE_MPI + // Type + int type; + MPI_Unpack(data_ptr, data.size(), &position, &type, 1, MPI_INT, + MPI_COMM_WORLD); + assert(type == ParticleType.at(this->type())); + + // nmaterials + int nmaterials = 0; + MPI_Unpack(data_ptr, data.size(), &position, &nmaterials, 1, MPI_UNSIGNED, + MPI_COMM_WORLD); + assert(nmaterials == materials.size()); + + // Material ID + MPI_Unpack(data_ptr, data.size(), &position, + &material_id_[mpm::ParticlePhase::Solid], 1, MPI_UNSIGNED, + MPI_COMM_WORLD); + MPI_Unpack(data_ptr, data.size(), &position, + &material_id_[mpm::ParticlePhase::Liquid], 1, MPI_UNSIGNED, + MPI_COMM_WORLD); + + // ID + MPI_Unpack(data_ptr, data.size(), &position, &id_, 1, MPI_UNSIGNED_LONG_LONG, + MPI_COMM_WORLD); + + // Solid Phase + // mass + MPI_Unpack(data_ptr, data.size(), &position, &mass_, 1, MPI_DOUBLE, + MPI_COMM_WORLD); + // volume + MPI_Unpack(data_ptr, data.size(), &position, &volume_, 1, MPI_DOUBLE, + MPI_COMM_WORLD); + // mass density + this->mass_density_ = mass_ / volume_; + + // pressure + double pressure; + MPI_Unpack(data_ptr, data.size(), &position, &pressure, 1, MPI_DOUBLE, + MPI_COMM_WORLD); + + // Coordinates + MPI_Unpack(data_ptr, data.size(), &position, coordinates_.data(), Tdim, + MPI_DOUBLE, MPI_COMM_WORLD); + // Displacement + MPI_Unpack(data_ptr, data.size(), &position, displacement_.data(), Tdim, + MPI_DOUBLE, MPI_COMM_WORLD); + // Natural size + MPI_Unpack(data_ptr, data.size(), &position, natural_size_.data(), Tdim, + MPI_DOUBLE, MPI_COMM_WORLD); + // Velocity + MPI_Unpack(data_ptr, data.size(), &position, velocity_.data(), Tdim, + MPI_DOUBLE, MPI_COMM_WORLD); + // Stress + MPI_Unpack(data_ptr, data.size(), &position, stress_.data(), 6, MPI_DOUBLE, + MPI_COMM_WORLD); + // Strain + MPI_Unpack(data_ptr, data.size(), &position, strain_.data(), 6, MPI_DOUBLE, + MPI_COMM_WORLD); + + // epsv + MPI_Unpack(data_ptr, data.size(), &position, &volumetric_strain_centroid_, 1, + MPI_DOUBLE, MPI_COMM_WORLD); + // cell id + MPI_Unpack(data_ptr, data.size(), &position, &cell_id_, 1, + MPI_UNSIGNED_LONG_LONG, MPI_COMM_WORLD); + // status + MPI_Unpack(data_ptr, data.size(), &position, &status_, 1, MPI_C_BOOL, + MPI_COMM_WORLD); + + // Assign materials + assert(material_id_[mpm::ParticlePhase::Solid] == + materials.at(mpm::ParticlePhase::Solid)->id()); + bool assign_mat = this->assign_material( + materials.at(mpm::ParticlePhase::Solid), mpm::ParticlePhase::Solid); + if (!assign_mat) + throw std::runtime_error( + "deserialize particle(): Solid material assignment failed"); + + // nstate vars + unsigned nstate_vars; + MPI_Unpack(data_ptr, data.size(), &position, &nstate_vars, 1, MPI_UNSIGNED, + MPI_COMM_WORLD); + + if (nstate_vars > 0) { + std::vector svars; + svars.reserve(nstate_vars); + MPI_Unpack(data_ptr, data.size(), &position, &svars[0], nstate_vars, + MPI_DOUBLE, MPI_COMM_WORLD); + + // Reinitialize state variables + auto mat_state_vars = (this->material(mpm::ParticlePhase::Solid)) + ->initialise_state_variables(); + if (mat_state_vars.size() != nstate_vars) + throw std::runtime_error( + "Deserialize particle(): Solid phase state_vars size mismatch"); + unsigned i = 0; + auto state_variables = + (this->material(mpm::ParticlePhase::Solid))->state_variables(); + for (const auto& state_var : state_variables) { + this->state_variables_[mpm::ParticlePhase::Solid].at(state_var) = + svars[i]; + ++i; + } + } + + // Liquid Phase + // liquid mass + MPI_Unpack(data_ptr, data.size(), &position, &liquid_mass_, 1, MPI_DOUBLE, + MPI_COMM_WORLD); + // Liquid mass Density + this->liquid_mass_density_ = liquid_mass_ / volume_; + + // Liquid velocity + MPI_Unpack(data_ptr, data.size(), &position, liquid_velocity_.data(), Tdim, + MPI_DOUBLE, MPI_COMM_WORLD); + // porosity + MPI_Unpack(data_ptr, data.size(), &position, &porosity_, 1, MPI_DOUBLE, + MPI_COMM_WORLD); + // liquid Saturation + MPI_Unpack(data_ptr, data.size(), &position, &liquid_saturation_, 1, + MPI_DOUBLE, MPI_COMM_WORLD); + + // Assign permeability + this->assign_permeability(); + + // Assign liquid materials + assert(material_id_[mpm::ParticlePhase::Liquid] == + materials.at(mpm::ParticlePhase::Liquid)->id()); + assign_mat = this->assign_material(materials.at(mpm::ParticlePhase::Liquid), + mpm::ParticlePhase::Liquid); + if (!assign_mat) + throw std::runtime_error( + "deserialize particle(): Liquid material assignment failed"); + + // nliquid state vars + unsigned nliquid_state_vars; + MPI_Unpack(data_ptr, data.size(), &position, &nliquid_state_vars, 1, + MPI_UNSIGNED, MPI_COMM_WORLD); + + if (nliquid_state_vars > 0) { + std::vector svars; + svars.reserve(nliquid_state_vars); + MPI_Unpack(data_ptr, data.size(), &position, &svars[0], nliquid_state_vars, + MPI_DOUBLE, MPI_COMM_WORLD); + + // Reinitialize state variables + auto mat_state_vars = (this->material(mpm::ParticlePhase::Liquid)) + ->initialise_state_variables(); + if (mat_state_vars.size() != nliquid_state_vars) + throw std::runtime_error( + "Deserialize particle(): Liquid phase state_vars size mismatch"); + unsigned i = 0; + auto state_variables = + (this->material(mpm::ParticlePhase::Liquid))->state_variables(); + for (const auto& state_var : state_variables) { + this->state_variables_[mpm::ParticlePhase::Liquid].at(state_var) = + svars[i]; + ++i; + } + } +#endif +} + +//! Map drag matrix to cell assuming linear-darcy drag force +template +bool mpm::TwoPhaseParticle::map_drag_matrix_to_cell() { + bool status = true; + try { + // Initialise drag force multiplier + VectorDim multiplier; + multiplier.setZero(); + // Porosity parameter + const double k_p = + std::pow(this->porosity_, 3) / std::pow((1. - this->porosity_), 2); + // Compute drag force multiplier + for (unsigned i = 0; i < Tdim; ++i) + multiplier(i) = this->porosity_ * this->porosity_ * 9.81 * + this->material(mpm::ParticlePhase::Liquid) + ->template property(std::string("density")) / + (this->permeability_(i) * k_p); + // Compute local drag matrix + cell_->compute_local_drag_matrix(shapefn_, volume_, multiplier); + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Map laplacian element matrix to cell (used in poisson equation LHS) +template +bool mpm::TwoPhaseParticle::map_laplacian_to_cell() { + bool status = true; + try { + // Compute multiplier + const double multiplier = + (1 - this->porosity_) / + this->material(mpm::ParticlePhase::Solid) + ->template property(std::string("density")) + + this->porosity_ / + this->material(mpm::ParticlePhase::Liquid) + ->template property(std::string("density")); + // Compute local matrix of Laplacian + cell_->compute_local_laplacian(dn_dx_, volume_, multiplier); + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Map poisson rhs element matrix to cell (used in poisson equation RHS) +template +bool mpm::TwoPhaseParticle::map_poisson_right_to_cell() { + bool status = true; + try { + // Compute local poisson rhs matrix for solid part + cell_->compute_local_poisson_right_twophase(mpm::ParticlePhase::Solid, + shapefn_, dn_dx_, volume_, + 1.0 - this->porosity_); + // Compute local poisson rhs matrix for liquid part + cell_->compute_local_poisson_right_twophase( + mpm::ParticlePhase::Liquid, shapefn_, dn_dx_, volume_, this->porosity_); + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Compute updated pressure of the particle based on nodal pressure +template +bool mpm::TwoPhaseParticle::compute_updated_pressure() { + bool status = true; + try { + double pressure_increment = 0; + for (unsigned i = 0; i < nodes_.size(); ++i) { + pressure_increment += shapefn_(i) * nodes_[i]->pressure_increment(); + } + + // Get interpolated nodal pressure + state_variables_[mpm::ParticlePhase::Liquid].at("pressure") = + state_variables_[mpm::ParticlePhase::Liquid].at("pressure") * + projection_param_ + + pressure_increment; + + // Overwrite pressure if free surface + if (this->free_surface()) + state_variables_[mpm::ParticlePhase::Liquid].at("pressure") = 0.0; + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Map correction matrix element matrix to cell (used to correct velocity) +template +bool mpm::TwoPhaseParticle::map_correction_matrix_to_cell() { + bool status = true; + try { + cell_->compute_local_correction_matrix_twophase(mpm::ParticlePhase::Solid, + shapefn_, dn_dx_, volume_, + 1.0 - this->porosity_); + cell_->compute_local_correction_matrix_twophase( + mpm::ParticlePhase::Liquid, shapefn_, dn_dx_, volume_, this->porosity_); + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + } + return status; +} \ No newline at end of file diff --git a/include/hdf5_particle.h b/include/particles/pod_particles/pod_particle.h similarity index 84% rename from include/hdf5_particle.h rename to include/particles/pod_particles/pod_particle.h index b5ebd54d5..9a7e3ba5b 100644 --- a/include/hdf5_particle.h +++ b/include/particles/pod_particles/pod_particle.h @@ -1,5 +1,5 @@ -#ifndef MPM_HDF5_H_ -#define MPM_HDF5_H_ +#ifndef MPM_POD_H_ +#define MPM_POD_H_ // HDF5 #include "hdf5.h" @@ -9,7 +9,7 @@ namespace mpm { // Define a struct of particle -typedef struct HDF5Particle { +typedef struct PODParticle { // Index mpm::Index id; // Mass @@ -44,13 +44,15 @@ typedef struct HDF5Particle { unsigned nstate_vars; // State variables (init to zero) double svars[20] = {0}; -} HDF5Particle; + // Destructor + virtual ~PODParticle() = default; +} PODParticle; -namespace hdf5 { +namespace pod { namespace particle { const hsize_t NFIELDS = 53; -const size_t dst_size = sizeof(HDF5Particle); +const size_t dst_size = sizeof(PODParticle); // Destination offset extern const size_t dst_offset[NFIELDS]; @@ -65,8 +67,8 @@ extern const char* field_names[NFIELDS]; extern const hid_t field_type[NFIELDS]; } // namespace particle -} // namespace hdf5 +} // namespace pod } // namespace mpm -#endif // MPM_HDF5_H_ +#endif // MPM_POD_H_ diff --git a/include/particles/pod_particles/pod_particle_twophase.h b/include/particles/pod_particles/pod_particle_twophase.h new file mode 100644 index 000000000..9b840bc82 --- /dev/null +++ b/include/particles/pod_particles/pod_particle_twophase.h @@ -0,0 +1,51 @@ +#ifndef MPM_POD_TWOPHASE_H_ +#define MPM_POD_TWOPHASE_H_ + +// POD Particle +#include "pod_particle.h" + +namespace mpm { +// Define a struct of particle +typedef struct PODParticleTwoPhase : PODParticle { + // Liquid Mass + double liquid_mass; + // Liquid Velocity + double liquid_velocity_x, liquid_velocity_y, liquid_velocity_z; + // Porosity + double porosity; + // Liquid Saturation + double liquid_saturation; + // Material id + unsigned liquid_material_id; + // Number of state variables + unsigned nliquid_state_vars; + // State variables (init to zero) + double liquid_svars[5] = {0}; + // Destructor + virtual ~PODParticleTwoPhase() = default; +} PODParticleTwoPhase; + +namespace pod { +namespace particletwophase { +const hsize_t NFIELDS = 66; + +const size_t dst_size = sizeof(PODParticleTwoPhase); + +// Destination offset +extern const size_t dst_offset[NFIELDS]; + +// Destination size +extern const size_t dst_sizes[NFIELDS]; + +// Define particle field information +extern const char* field_names[NFIELDS]; + +// Initialize field types +extern const hid_t field_type[NFIELDS]; + +} // namespace particletwophase +} // namespace pod + +} // namespace mpm + +#endif // MPM_POD_TWOPHASE_H_ diff --git a/include/solvers/mpm.h b/include/solvers/mpm.h index 070917dd2..354f248be 100644 --- a/include/solvers/mpm.h +++ b/include/solvers/mpm.h @@ -49,6 +49,9 @@ class MPM { // Initialise particles virtual void initialise_particles() = 0; + // Initialise particle types + virtual void initialise_particle_types() = 0; + // Initialise materials virtual void initialise_materials() = 0; @@ -67,6 +70,9 @@ class MPM { //! Write HDF5 files virtual void write_hdf5(mpm::Index step, mpm::Index max_steps) = 0; + //! Write HDF5 files for twophase particles + virtual void write_hdf5_twophase(mpm::Index step, mpm::Index max_steps) = 0; + #ifdef USE_VTK //! Write VTK files virtual void write_vtk(mpm::Index step, mpm::Index max_steps) = 0; diff --git a/include/solvers/mpm_base.h b/include/solvers/mpm_base.h index abf0d9c4e..2dff38772 100644 --- a/include/solvers/mpm_base.h +++ b/include/solvers/mpm_base.h @@ -26,6 +26,7 @@ #include "mpm_scheme_usf.h" #include "mpm_scheme_usl.h" #include "particle.h" +#include "solver_base.h" #include "vector.h" namespace mpm { @@ -57,6 +58,9 @@ class MPMBase : public MPM { //! Initialise particles void initialise_particles() override; + //! Initialise particle types + void initialise_particle_types() override; + //! Initialise materials void initialise_materials() override; @@ -85,6 +89,9 @@ class MPMBase : public MPM { //! Write HDF5 files void write_hdf5(mpm::Index step, mpm::Index max_steps) override; + //! Write HDF5 files + void write_hdf5_twophase(mpm::Index step, mpm::Index max_steps) override; + //! Domain decomposition //! \param[in] initial_step Start of simulation or later steps void mpi_domain_decompose(bool initial_step = false) override; @@ -100,6 +107,17 @@ class MPMBase : public MPM { //! Particle velocity constraints void particle_velocity_constraints(); + protected: + //! Initialise implicit solver + //! \param[in] lin_solver_props Linear solver properties + //! \param[in, out] linear_solver Linear solver map + void initialise_linear_solver( + const Json& lin_solver_props, + tsl::robin_map< + std::string, + std::shared_ptr>>>& + linear_solver); + private: //! Return if a mesh will be isoparametric or not //! \retval isoparametric Status of mesh type @@ -128,6 +146,12 @@ class MPMBase : public MPM { void nodal_frictional_constraints( const Json& mesh_prop, const std::shared_ptr>& mesh_io); + //! Nodal pressure constraints + //! \param[in] mesh_prop Mesh properties + //! \param[in] mesh_io Mesh IO handle + void nodal_pressure_constraints( + const Json& mesh_prop, const std::shared_ptr>& mesh_io); + //! Cell entity sets //! \param[in] mesh_prop Mesh properties //! \param[in] check Check duplicates @@ -152,6 +176,13 @@ class MPMBase : public MPM { const Json& mesh_prop, const std::shared_ptr>& particle_io); + // Particles pore pressures + //! \param[in] mesh_prop Mesh properties + //! \param[in] particle_io Particle IO handle + void particles_pore_pressures( + const Json& mesh_prop, + const std::shared_ptr>& particle_io); + //! Initialise damping //! \param[in] damping_props Damping properties bool initialise_damping(const Json& damping_props); @@ -192,6 +223,8 @@ class MPMBase : public MPM { std::shared_ptr> mesh_; //! Constraints object std::shared_ptr> constraints_; + //! Particle types + std::set particle_types_; //! Materials std::map>> materials_; //! Mathematical functions @@ -208,6 +241,8 @@ class MPMBase : public MPM { double damping_factor_{0.}; //! Locate particles bool locate_particles_{true}; + //! Nonlocal node neighbourhood + unsigned node_neighbourhood_{0}; #ifdef USE_GRAPH_PARTITIONING // graph pass the address of the container of cell diff --git a/include/solvers/mpm_base.tcc b/include/solvers/mpm_base.tcc index f829b5f95..8c1ae635c 100644 --- a/include/solvers/mpm_base.tcc +++ b/include/solvers/mpm_base.tcc @@ -30,6 +30,7 @@ mpm::MPMBase::MPMBase(const std::shared_ptr& io) : mpm::MPM(io) { // Vector variables {"displacements", VariableType::Vector}, {"velocities", VariableType::Vector}, + {"normals", VariableType::Vector}, // Tensor variables {"strains", VariableType::Tensor}, {"stresses", VariableType::Tensor}}; @@ -236,6 +237,9 @@ void mpm::MPMBase::initialise_mesh() { // Read and assign friction constraints this->nodal_frictional_constraints(mesh_props, mesh_io); + // Read and assign pressure constraints + this->nodal_pressure_constraints(mesh_props, mesh_io); + // Initialise cell auto cells_begin = std::chrono::steady_clock::now(); // Shape function name @@ -268,6 +272,20 @@ void mpm::MPMBase::initialise_mesh() { .count()); } +// Initialise particle types +template +void mpm::MPMBase::initialise_particle_types() { + // Get particles properties + auto json_particles = io_->json_object("particles"); + + for (const auto& json_particle : json_particles) { + // Gather particle types + auto particle_type = + json_particle["generator"]["particle_type"].template get(); + particle_types_.insert(particle_type); + } +} + // Initialise particles template void mpm::MPMBase::initialise_particles() { @@ -305,6 +323,9 @@ void mpm::MPMBase::initialise_particles() { "mpm::base::init_particles() Generate particles failed"); } + // Gather particle types + this->initialise_particle_types(); + auto particles_gen_end = std::chrono::steady_clock::now(); console_->info("Rank {} Generate particles: {} ms", mpi_rank, std::chrono::duration_cast( @@ -348,6 +369,9 @@ void mpm::MPMBase::initialise_particles() { // Read and assign particles stresses this->particles_stresses(mesh_props, particle_io); + // Read and assign particles initial pore pressure + this->particles_pore_pressures(mesh_props, particle_io); + auto particles_volume_end = std::chrono::steady_clock::now(); console_->info("Rank {} Read volume, velocity and stresses: {} ms", mpi_rank, std::chrono::duration_cast( @@ -426,14 +450,14 @@ template bool mpm::MPMBase::checkpoint_resume() { bool checkpoint = true; try { - // TODO: Set phase - const unsigned phase = 0; - int mpi_rank = 0; #ifdef USE_MPI MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); #endif + // Gather particle types + this->initialise_particle_types(); + if (!analysis_["resume"]["resume"].template get()) throw std::runtime_error("Resume analysis option is disabled!"); @@ -443,15 +467,17 @@ bool mpm::MPMBase::checkpoint_resume() { this->step_ = analysis_["resume"]["step"].template get(); // Input particle h5 file for resume - std::string attribute = "particles"; - std::string extension = ".h5"; + for (const auto ptype : particle_types_) { + std::string attribute = mpm::ParticlePODTypeName.at(ptype); + std::string extension = ".h5"; - auto particles_file = - io_->output_file(attribute, extension, uuid_, step_, this->nsteps_) - .string(); + auto particles_file = + io_->output_file(attribute, extension, uuid_, step_, this->nsteps_) + .string(); - // Load particle information from file - mesh_->read_particles_hdf5(phase, particles_file); + // Load particle information from file + mesh_->read_particles_hdf5(particles_file, attribute, ptype); + } // Clear all particle ids mesh_->iterate_over_cells( @@ -480,15 +506,31 @@ bool mpm::MPMBase::checkpoint_resume() { //! Write HDF5 files template void mpm::MPMBase::write_hdf5(mpm::Index step, mpm::Index max_steps) { - // Write input geometry to vtk file - std::string attribute = "particles"; + // Write hdf5 file for single phase particle + for (const auto ptype : particle_types_) { + std::string attribute = mpm::ParticlePODTypeName.at(ptype); + std::string extension = ".h5"; + + auto particles_file = + io_->output_file(attribute, extension, uuid_, step, max_steps).string(); + + // Load particle information from file + mesh_->write_particles_hdf5(particles_file); + } +} + +//! Write HDF5 files for twophase particles +template +void mpm::MPMBase::write_hdf5_twophase(mpm::Index step, + mpm::Index max_steps) { + // Write hdf5 file for single phase particle + std::string attribute = "twophase_particles"; std::string extension = ".h5"; auto particles_file = io_->output_file(attribute, extension, uuid_, step, max_steps).string(); - const unsigned phase = 0; - mesh_->write_particles_hdf5(phase, particles_file); + mesh_->write_particles_hdf5_twophase(particles_file); } #ifdef USE_VTK @@ -937,6 +979,58 @@ void mpm::MPMBase::nodal_frictional_constraints( } } +// Nodal pressure constraints +template +void mpm::MPMBase::nodal_pressure_constraints( + const Json& mesh_props, const std::shared_ptr>& mesh_io) { + try { + // Read and assign pressure constraints + if (mesh_props.find("boundary_conditions") != mesh_props.end() && + mesh_props["boundary_conditions"].find("pressure_constraints") != + mesh_props["boundary_conditions"].end()) { + + // Iterate over pressure constraints + for (const auto& constraints : + mesh_props["boundary_conditions"]["pressure_constraints"]) { + // Pore pressure constraint phase indice + unsigned constraint_phase = constraints["phase_id"]; + + // Pore pressure constraints are specified in a file + if (constraints.find("file") != constraints.end()) { + std::string pressure_constraints_file = + constraints.at("file").template get(); + bool ppressure_constraints = + constraints_->assign_nodal_pressure_constraints( + constraint_phase, + mesh_io->read_pressure_constraints( + io_->file_name(pressure_constraints_file))); + if (!ppressure_constraints) + throw std::runtime_error( + "Pore pressure constraints are not properly assigned"); + } else { + // Get the math function + std::shared_ptr pfunction = nullptr; + if (constraints.find("math_function_id") != constraints.end()) + pfunction = math_functions_.at( + constraints.at("math_function_id").template get()); + // Set id + int nset_id = constraints.at("nset_id").template get(); + // Pressure + double pressure = constraints.at("pressure").template get(); + // Add pressure constraint to mesh + constraints_->assign_nodal_pressure_constraint( + pfunction, nset_id, constraint_phase, pressure); + } + } + } else + throw std::runtime_error("Pressure constraints JSON not found"); + + } catch (std::exception& exception) { + console_->warn("#{}: Nodal pressure constraints are undefined {} ", + __LINE__, exception.what()); + } +} + //! Cell entity sets template void mpm::MPMBase::cell_entity_sets(const Json& mesh_props, @@ -1084,6 +1178,68 @@ void mpm::MPMBase::particles_stresses( } } +// Particles pore pressures +template +void mpm::MPMBase::particles_pore_pressures( + const Json& mesh_props, + const std::shared_ptr>& particle_io) { + try { + if (mesh_props.find("particles_pore_pressures") != mesh_props.end()) { + // Get generator type + const std::string type = mesh_props["particles_pore_pressures"]["type"] + .template get(); + // Assign initial pore pressure by file + if (type == "file") { + std::string fparticles_pore_pressures = + mesh_props["particles_pore_pressures"]["location"] + .template get(); + if (!io_->file_name(fparticles_pore_pressures).empty()) { + // Read and assign particles pore pressures + if (!mesh_->assign_particles_pore_pressures( + particle_io->read_particles_scalar_properties( + io_->file_name(fparticles_pore_pressures)))) + throw std::runtime_error( + "Particles pore pressures are not properly assigned"); + } else + throw std::runtime_error("Particle pore pressures JSON not found"); + } else if (type == "water_table") { + // Initialise water tables + std::map reference_points; + // Vertical direction + const unsigned dir_v = mesh_props["particles_pore_pressures"]["dir_v"] + .template get(); + // Horizontal direction + const unsigned dir_h = mesh_props["particles_pore_pressures"]["dir_h"] + .template get(); + // Iterate over water tables + for (const auto& water_table : + mesh_props["particles_pore_pressures"]["water_tables"]) { + // Position coordinate + double position = water_table.at("position").template get(); + // Direction + double h0 = water_table.at("h0").template get(); + // Add reference points to mesh + reference_points.insert(std::make_pair( + static_cast(position), static_cast(h0))); + } + // Initialise particles pore pressures by watertable + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::initialise_pore_pressure_watertable, + std::placeholders::_1, dir_v, dir_h, this->gravity_, + reference_points)); + } else + throw std::runtime_error( + "Particle pore pressures generator type is not properly " + "specified"); + } else + throw std::runtime_error("Particle pore pressure JSON not found"); + + } catch (std::exception& exception) { + console_->warn("#{}: Particle pore pressures are undefined {} ", __LINE__, + exception.what()); + } +} + //! Particle entity sets template void mpm::MPMBase::particle_entity_sets(bool check_duplicates) { @@ -1203,6 +1359,12 @@ void mpm::MPMBase::pressure_smoothing(unsigned phase) { std::bind(&mpm::ParticleBase::map_pressure_to_nodes, std::placeholders::_1, phase)); + // Apply pressure constraint + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::apply_pressure_constraint, + std::placeholders::_1, phase, this->dt_, this->step_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + #ifdef USE_MPI int mpi_size = 1; @@ -1224,3 +1386,61 @@ void mpm::MPMBase::pressure_smoothing(unsigned phase) { std::bind(&mpm::ParticleBase::compute_pressure_smoothing, std::placeholders::_1, phase)); } + +//! MPM implicit solver initialization +template +void mpm::MPMBase::initialise_linear_solver( + const Json& lin_solver_props, + tsl::robin_map< + std::string, + std::shared_ptr>>>& + linear_solver) { + // Iterate over specific solver settings + for (const auto& solver : lin_solver_props) { + std::string dof = solver["dof"].template get(); + std::string solver_type = solver["solver_type"].template get(); + // NOTE: Only KrylovPETSC solver is supported for MPI +#ifdef USE_MPI + // Get number of MPI ranks + int mpi_size = 1; + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); + if (solver_type != "KrylovPETSC" && mpi_size > 1) { + console_->warn( + "The linear solver for DOF:\'{}\' in MPI setting is " + "automatically set to default: \'KrylovPETSC\'. Only " + "\'KrylovPETSC\' solver is supported for MPI.", + dof); + solver_type = "KrylovPETSC"; + } +#endif + unsigned max_iter = solver["max_iter"].template get(); + double tolerance = solver["tolerance"].template get(); + auto lin_solver = + Factory>, unsigned, + double>::instance() + ->create(solver_type, std::move(max_iter), std::move(tolerance)); + + // Specific settings + if (solver.contains("sub_solver_type")) + lin_solver->set_sub_solver_type( + solver["sub_solver_type"].template get()); + if (solver.contains("preconditioner_type")) + lin_solver->set_preconditioner_type( + solver["preconditioner_type"].template get()); + if (solver.contains("abs_tolerance")) + lin_solver->set_abs_tolerance( + solver["abs_tolerance"].template get()); + if (solver.contains("div_tolerance")) + lin_solver->set_div_tolerance( + solver["div_tolerance"].template get()); + if (solver.contains("verbosity")) + lin_solver->set_verbosity(solver["verbosity"].template get()); + + // Add solver set to map + linear_solver.insert( + std::pair< + std::string, + std::shared_ptr>>>( + dof, lin_solver)); + } +} \ No newline at end of file diff --git a/include/solvers/mpm_explicit.tcc b/include/solvers/mpm_explicit.tcc index f12728412..2b1ff38ff 100644 --- a/include/solvers/mpm_explicit.tcc +++ b/include/solvers/mpm_explicit.tcc @@ -76,37 +76,38 @@ bool mpm::MPMExplicit::solve() { // Initialise mesh this->initialise_mesh(); - // Initialise particles - if (!resume) this->initialise_particles(); - - // Create nodal properties - if (interface_) mesh_->create_nodal_properties(); - - // Compute mass - if (!resume) - mesh_->iterate_over_particles(std::bind( - &mpm::ParticleBase::compute_mass, std::placeholders::_1)); - // Check point resume if (resume) { - this->checkpoint_resume(); + bool check_resume = this->checkpoint_resume(); + if (!check_resume) resume = false; + } + + // Resume or Initialise + if (resume) { mesh_->resume_domain_cell_ranks(); #ifdef USE_MPI #ifdef USE_GRAPH_PARTITIONING MPI_Barrier(MPI_COMM_WORLD); #endif #endif + //! Particle entity sets and velocity constraints + this->particle_entity_sets(false); + this->particle_velocity_constraints(); } else { + // Initialise particles + this->initialise_particles(); + + // Compute mass + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_mass, std::placeholders::_1)); + // Domain decompose bool initial_step = (resume == true) ? false : true; this->mpi_domain_decompose(initial_step); } - //! Particle entity sets and velocity constraints - if (resume) { - this->particle_entity_sets(false); - this->particle_velocity_constraints(); - } + // Create nodal properties + if (interface_) mesh_->create_nodal_properties(); // Initialise loading conditions this->initialise_loads(); diff --git a/include/solvers/mpm_explicit_twophase.h b/include/solvers/mpm_explicit_twophase.h new file mode 100644 index 000000000..c3598b845 --- /dev/null +++ b/include/solvers/mpm_explicit_twophase.h @@ -0,0 +1,87 @@ +#ifndef MPM_MPM_EXPLICIT_TWOPHASE_H_ +#define MPM_MPM_EXPLICIT_TWOPHASE_H_ + +#ifdef USE_GRAPH_PARTITIONING +#include "graph.h" +#endif + +#include "solvers/mpm_base.h" + +namespace mpm { + +//! MPMExplicit class +//! \brief A class that implements the fully explicit one phase mpm +//! \details A single-phase explicit MPM +//! \tparam Tdim Dimension +template +class MPMExplicitTwoPhase : public MPMBase { + public: + //! Default constructor + MPMExplicitTwoPhase(const std::shared_ptr& io); + + //! Solve + bool solve() override; + + //! Compute stress strain + void compute_stress_strain(); + + protected: + // Generate a unique id for the analysis + using mpm::MPMBase::uuid_; + //! Time step size + using mpm::MPMBase::dt_; + //! Current step + using mpm::MPMBase::step_; + //! Number of steps + using mpm::MPMBase::nsteps_; + //! Number of steps + using mpm::MPMBase::nload_balance_steps_; + //! Output steps + using mpm::MPMBase::output_steps_; + //! A unique ptr to IO object + using mpm::MPMBase::io_; + //! JSON analysis object + using mpm::MPMBase::analysis_; + //! JSON post-process object + using mpm::MPMBase::post_process_; + //! Logger + using mpm::MPMBase::console_; + +#ifdef USE_GRAPH_PARTITIONING + //! Graph + using mpm::MPMBase::graph_; +#endif + + //! velocity update + using mpm::MPMBase::velocity_update_; + //! Gravity + using mpm::MPMBase::gravity_; + //! Mesh object + using mpm::MPMBase::mesh_; + //! Materials + using mpm::MPMBase::materials_; + //! Node concentrated force + using mpm::MPMBase::set_node_concentrated_force_; + //! Damping type + using mpm::MPMBase::damping_type_; + //! Damping factor + using mpm::MPMBase::damping_factor_; + //! Locate particles + using mpm::MPMBase::locate_particles_; + + private: + //! Pressure smoothing + bool pressure_smoothing_{false}; + //! Pore pressure smoothing + bool pore_pressure_smoothing_{false}; + //! Compute free surface + std::string free_surface_detection_; + //! Volume tolerance for free surface + double volume_tolerance_{0.}; + +}; // MPMExplicit class +} // namespace mpm + +#include "mpm_explicit_twophase.tcc" + +#endif // MPM_MPM_EXPLICIT_H_ diff --git a/include/solvers/mpm_explicit_twophase.tcc b/include/solvers/mpm_explicit_twophase.tcc new file mode 100644 index 000000000..cc684ce19 --- /dev/null +++ b/include/solvers/mpm_explicit_twophase.tcc @@ -0,0 +1,382 @@ +//! Constructor +template +mpm::MPMExplicitTwoPhase::MPMExplicitTwoPhase( + const std::shared_ptr& io) + : mpm::MPMBase(io) { + //! Logger + console_ = spdlog::get("MPMExplicitTwoPhase"); +} + +//! MPM Explicit compute stress strain +template +void mpm::MPMExplicitTwoPhase::compute_stress_strain() { + // Iterate over each particle to calculate strain of soil_skeleton + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_strain, std::placeholders::_1, dt_)); + // Iterate over each particle to update particle volume + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::update_volume, std::placeholders::_1)); + // Iterate over each particle to compute stress of soil skeleton + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_stress, std::placeholders::_1)); + // Pressure smoothing + if (pressure_smoothing_) this->pressure_smoothing(mpm::ParticlePhase::Solid); + + // Iterate over each particle to update porosity + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::update_porosity, std::placeholders::_1, dt_)); + // Iterate over each particle to compute pore pressure + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::compute_pore_pressure, + std::placeholders::_1, dt_)); + // Pore pressure smoothing + if (pore_pressure_smoothing_) { + this->pressure_smoothing(mpm::ParticlePhase::Liquid); + } +} + +//! MPM Explicit solver +template +bool mpm::MPMExplicitTwoPhase::solve() { + bool status = true; + + console_->info("MPM analysis type {}", io_->analysis_type()); + + // Initialise MPI rank and size + int mpi_rank = 0; + int mpi_size = 1; + +#ifdef USE_MPI + // Get MPI rank + MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); + // Get number of MPI ranks + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); +#endif + + // Test if checkpoint resume is needed + bool resume = false; + if (analysis_.find("resume") != analysis_.end()) + resume = analysis_["resume"]["resume"].template get(); + + // Pressure smoothing + if (analysis_.find("pressure_smoothing") != analysis_.end()) + pressure_smoothing_ = + analysis_.at("pressure_smoothing").template get(); + + // Pore pressure smoothing + if (analysis_.find("pore_pressure_smoothing") != analysis_.end()) + pore_pressure_smoothing_ = + analysis_.at("pore_pressure_smoothing").template get(); + + // Free surface detection + free_surface_detection_ = "none"; + if (analysis_.find("free_surface_detection") != analysis_.end()) { + // Get method to detect free surface detection + free_surface_detection_ = "density"; + if (analysis_["free_surface_detection"].contains("type")) + free_surface_detection_ = analysis_["free_surface_detection"]["type"] + .template get(); + // Get volume tolerance for free surface + volume_tolerance_ = analysis_["free_surface_detection"]["volume_tolerance"] + .template get(); + } + + // Initialise material + this->initialise_materials(); + + // Initialise mesh + this->initialise_mesh(); + + // Check point resume + if (resume) { + bool check_resume = this->checkpoint_resume(); + if (!check_resume) resume = false; + } + + // Resume or Initialise + if (resume) { + mesh_->resume_domain_cell_ranks(); +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + MPI_Barrier(MPI_COMM_WORLD); +#endif +#endif + + //! Particle entity sets and velocity constraints + this->particle_entity_sets(false); + this->particle_velocity_constraints(); + } else { + // Initialise particles + this->initialise_particles(); + + // Assign porosity + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::assign_porosity, std::placeholders::_1)); + + // Assign permeability + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::assign_permeability, std::placeholders::_1)); + + // Compute mass for each phase + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_mass, std::placeholders::_1)); + + // Domain decompose + bool initial_step = (resume == true) ? false : true; + this->mpi_domain_decompose(initial_step); + } + + // Initialise loading conditions + this->initialise_loads(); + + auto solver_begin = std::chrono::steady_clock::now(); + // Main loop + for (; step_ < nsteps_; ++step_) { + + if (mpi_rank == 0) console_->info("Step: {} of {}.\n", step_, nsteps_); + +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + // Run load balancer at a specified frequency + if (step_ % nload_balance_steps_ == 0 && step_ != 0) + this->mpi_domain_decompose(false); +#endif +#endif + + // Inject particles + mesh_->inject_particles(this->step_ * this->dt_); + +#pragma omp parallel sections + { + // Spawn a task for initialising nodes and cells +#pragma omp section + { + // Initialise nodes + mesh_->iterate_over_nodes(std::bind( + &mpm::NodeBase::initialise_twophase, std::placeholders::_1)); + + mesh_->iterate_over_cells( + std::bind(&mpm::Cell::activate_nodes, std::placeholders::_1)); + } + // Spawn a task for particles +#pragma omp section + { + // Iterate over each particle to compute shapefn + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_shapefn, std::placeholders::_1)); + } + } // Wait to complete + + // Assign mass and momentum to nodes + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_mass_momentum_to_nodes, + std::placeholders::_1)); + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce nodal mass for solid phase + mesh_->template nodal_halo_exchange( + std::bind(&mpm::NodeBase::mass, std::placeholders::_1, + mpm::NodePhase::NSolid), + std::bind(&mpm::NodeBase::update_mass, std::placeholders::_1, + false, mpm::NodePhase::NSolid, std::placeholders::_2)); + // MPI all reduce nodal momentum for solid phase + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::momentum, std::placeholders::_1, + mpm::NodePhase::NSolid), + std::bind(&mpm::NodeBase::update_momentum, + std::placeholders::_1, false, mpm::NodePhase::NSolid, + std::placeholders::_2)); + + // MPI all reduce nodal mass for liquid phase + mesh_->template nodal_halo_exchange( + std::bind(&mpm::NodeBase::mass, std::placeholders::_1, + mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_mass, std::placeholders::_1, + false, mpm::NodePhase::NLiquid, std::placeholders::_2)); + // MPI all reduce nodal momentum for liquid phase + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::momentum, std::placeholders::_1, + mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_momentum, + std::placeholders::_1, false, mpm::NodePhase::NLiquid, + std::placeholders::_2)); + } +#endif + + // Compute free surface cells, nodes, and particles + if (free_surface_detection_ != "none") { + mesh_->compute_free_surface(free_surface_detection_, volume_tolerance_); + + // Spawn a task for initializing pressure at free surface +#pragma omp parallel sections + { +#pragma omp section + { + // Assign initial pressure for all free-surface particle + mesh_->iterate_over_particles_predicate( + std::bind(&mpm::ParticleBase::assign_pressure, + std::placeholders::_1, 0.0, mpm::ParticlePhase::Liquid), + std::bind(&mpm::ParticleBase::free_surface, + std::placeholders::_1)); + } + } // Wait to complete + } + + // Compute nodal velocity at the begining of time step + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::compute_velocity, + std::placeholders::_1), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Update stress first + if (this->stress_update_ == "usf") this->compute_stress_strain(); + + // Spawn a task for external force +#pragma omp parallel sections + { +#pragma omp section + { + // Iterate over each particle to compute nodal body force + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_body_force, + std::placeholders::_1, this->gravity_)); + + // Apply particle traction and map to nodes + mesh_->apply_traction_on_particles(this->step_ * this->dt_); + + // Iterate over each node to add concentrated node force to external + // force + if (set_node_concentrated_force_) + mesh_->iterate_over_nodes( + std::bind(&mpm::NodeBase::apply_concentrated_force, + std::placeholders::_1, mpm::ParticlePhase::Solid, + (this->step_ * this->dt_))); + } + +#pragma omp section + { + // Spawn a task for internal force + // Iterate over each particle to compute nodal internal force + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_internal_force, + std::placeholders::_1)); + } + +#pragma omp section + { + // Iterate over particles to compute nodal drag force coefficient + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_drag_force_coefficient, + std::placeholders::_1)); + } + } // Wait for tasks to finish + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce external force of mixture + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::external_force, std::placeholders::_1, + mpm::NodePhase::NMixture), + std::bind(&mpm::NodeBase::update_external_force, + std::placeholders::_1, false, mpm::NodePhase::NMixture, + std::placeholders::_2)); + // MPI all reduce external force of pore fluid + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::external_force, std::placeholders::_1, + mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_external_force, + std::placeholders::_1, false, mpm::NodePhase::NLiquid, + std::placeholders::_2)); + + // MPI all reduce internal force of mixture + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::internal_force, std::placeholders::_1, + mpm::NodePhase::NMixture), + std::bind(&mpm::NodeBase::update_internal_force, + std::placeholders::_1, false, mpm::NodePhase::NMixture, + std::placeholders::_2)); + // MPI all reduce internal force of pore liquid + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::internal_force, std::placeholders::_1, + mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_internal_force, + std::placeholders::_1, false, mpm::NodePhase::NLiquid, + std::placeholders::_2)); + + // MPI all reduce drag force + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::drag_force_coefficient, + std::placeholders::_1), + std::bind(&mpm::NodeBase::update_drag_force_coefficient, + std::placeholders::_1, false, std::placeholders::_2)); + } +#endif + + // Check if damping has been specified and accordingly Iterate over + // active nodes to compute acceleratation and velocity + if (damping_type_ == mpm::Damping::Cundall) + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase:: + compute_acceleration_velocity_twophase_explicit_cundall, + std::placeholders::_1, this->dt_, damping_factor_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + else + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase< + Tdim>::compute_acceleration_velocity_twophase_explicit, + std::placeholders::_1, this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Update particle position and kinematics + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::compute_updated_position, + std::placeholders::_1, this->dt_, velocity_update_)); + + // Apply particle velocity constraints + mesh_->apply_particle_velocity_constraints(); + + // Update Stress Last + if (this->stress_update_ == "usl") this->compute_stress_strain(); + + // Locate particles + auto unlocatable_particles = mesh_->locate_particles_mesh(); + + if (!unlocatable_particles.empty() && this->locate_particles_) + throw std::runtime_error("Particle outside the mesh domain"); + // If unable to locate particles remove particles + if (!unlocatable_particles.empty() && !this->locate_particles_) + for (const auto& remove_particle : unlocatable_particles) + mesh_->remove_particle(remove_particle); + +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + mesh_->transfer_halo_particles(); + MPI_Barrier(MPI_COMM_WORLD); +#endif +#endif + + if (step_ % output_steps_ == 0) { + // HDF5 outputs + this->write_hdf5_twophase(this->step_, this->nsteps_); +#ifdef USE_VTK + // VTK outputs + this->write_vtk(this->step_, this->nsteps_); +#endif +#ifdef USE_PARTIO + // Partio outputs + this->write_partio(this->step_, this->nsteps_); +#endif + } + } + auto solver_end = std::chrono::steady_clock::now(); + console_->info("Rank {}, Explicit {} solver duration: {} ms", mpi_rank, + (this->stress_update_ == "usl" ? "USL" : "USF"), + std::chrono::duration_cast( + solver_end - solver_begin) + .count()); + + return status; +} \ No newline at end of file diff --git a/include/solvers/mpm_semi_implicit_navierstokes.h b/include/solvers/mpm_semi_implicit_navierstokes.h new file mode 100644 index 000000000..74da575f4 --- /dev/null +++ b/include/solvers/mpm_semi_implicit_navierstokes.h @@ -0,0 +1,100 @@ +#ifndef MPM_MPM_SEMI_IMPLICIT_NAVIER_STOKES_H_ +#define MPM_MPM_SEMI_IMPLICIT_NAVIER_STOKES_H_ + +#ifdef USE_GRAPH_PARTITIONING +#include "graph.h" +#endif + +#include "mpm_base.h" + +#include "assembler_base.h" +#include "solver_base.h" + +namespace mpm { + +//! MPMSemiImplicit Navier Stokes class +//! \brief A class that implements the fractional step navier-stokes mpm +//! \tparam Tdim Dimension +template +class MPMSemiImplicitNavierStokes : public MPMBase { + public: + //! Default constructor + MPMSemiImplicitNavierStokes(const std::shared_ptr& io); + + //! Return matrix assembler pointer + std::shared_ptr> matrix_assembler() { + return assembler_; + } + + //! Solve + bool solve() override; + + //! Class private functions + private: + //! Initialise matrix + bool initialise_matrix(); + + //! Initialise matrix + bool reinitialise_matrix(); + + //! Compute poisson equation + bool compute_poisson_equation(); + + //! Compute corrected velocity + bool compute_correction_force(); + + //! Class private variables + private: + // Generate a unique id for the analysis + using mpm::MPMBase::uuid_; + //! Time step size + using mpm::MPMBase::dt_; + //! Current step + using mpm::MPMBase::step_; + //! Number of steps + using mpm::MPMBase::nsteps_; + //! Number of steps + using mpm::MPMBase::nload_balance_steps_; + //! Output steps + using mpm::MPMBase::output_steps_; + //! A unique ptr to IO object + using mpm::MPMBase::io_; + //! JSON analysis object + using mpm::MPMBase::analysis_; + //! JSON post-process object + using mpm::MPMBase::post_process_; + //! Logger + using mpm::MPMBase::console_; + //! Stress update + using mpm::MPMBase::stress_update_; + //! velocity update + using mpm::MPMBase::velocity_update_; + //! Gravity + using mpm::MPMBase::gravity_; + //! Mesh object + using mpm::MPMBase::mesh_; + //! Materials + using mpm::MPMBase::materials_; + //! Nonlocal neighbourhood + using mpm::MPMBase::node_neighbourhood_; + //! Pressure smoothing + bool pressure_smoothing_{false}; + // Projection method parameter (beta) + double beta_{1}; + //! Assembler object + std::shared_ptr> assembler_; + //! Linear solver object + tsl::robin_map>>> + linear_solver_; + //! Method to detect free surface detection + std::string free_surface_detection_; + //! Volume tolerance for free surface + double volume_tolerance_{0}; + +}; // MPMSemiImplicit class +} // namespace mpm + +#include "mpm_semi_implicit_navierstokes.tcc" + +#endif // MPM_MPM_SEMI_IMPLICIT_NAVIER_STOKES_H_ diff --git a/include/solvers/mpm_semi_implicit_navierstokes.tcc b/include/solvers/mpm_semi_implicit_navierstokes.tcc new file mode 100644 index 000000000..c444abd75 --- /dev/null +++ b/include/solvers/mpm_semi_implicit_navierstokes.tcc @@ -0,0 +1,522 @@ +//! Constructor +template +mpm::MPMSemiImplicitNavierStokes::MPMSemiImplicitNavierStokes( + const std::shared_ptr& io) + : mpm::MPMBase(io) { + //! Logger + console_ = spdlog::get("MPMSemiImplicitNavierStokes"); +} + +//! MPM semi-implicit navier-stokes solver +template +bool mpm::MPMSemiImplicitNavierStokes::solve() { + bool status = true; + + console_->info("MPM analysis type {}", io_->analysis_type()); + + // Initialise MPI rank and size + int mpi_rank = 0; + int mpi_size = 1; + +#ifdef USE_MPI + // Get MPI rank + MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); + // Get number of MPI ranks + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); +#endif + + // This solver consider only fluid variables + // NOTE: Due to indexing purposes + const unsigned fluid = mpm::ParticlePhase::SinglePhase; + + // Test if checkpoint resume is needed + bool resume = false; + if (analysis_.find("resume") != analysis_.end()) + resume = analysis_["resume"]["resume"].template get(); + + // Pressure smoothing + if (analysis_.find("pressure_smoothing") != analysis_.end()) + pressure_smoothing_ = analysis_["pressure_smoothing"].template get(); + + // Projection method parameter (beta) + if (analysis_.find("semi_implicit") != analysis_.end()) + beta_ = analysis_["semi_implicit"]["beta"].template get(); + + // Initialise material + this->initialise_materials(); + + // Initialise mesh + this->initialise_mesh(); + + // Check point resume + if (resume) { + bool check_resume = this->checkpoint_resume(); + if (!check_resume) resume = false; + } + + // Check point resume + if (resume) { + mesh_->resume_domain_cell_ranks(); +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + MPI_Barrier(MPI_COMM_WORLD); +#endif +#endif + + //! Particle entity sets and velocity constraints + this->particle_entity_sets(false); + this->particle_velocity_constraints(); + + } else { + // Initialise particles + this->initialise_particles(); + + // Compute mass for single phase fluid + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_mass, std::placeholders::_1)); + + // Domain decompose + bool initial_step = (resume == true) ? false : true; + this->mpi_domain_decompose(initial_step); + } + + // Initialise loading conditions + this->initialise_loads(); + + // Initialise matrix + bool matrix_status = this->initialise_matrix(); + if (!matrix_status) { + status = false; + throw std::runtime_error("Initialisation of matrix failed"); + } + + // Assign beta to each particle + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::assign_projection_parameter, + std::placeholders::_1, beta_)); + + auto solver_begin = std::chrono::steady_clock::now(); + // Main loop + for (; step_ < nsteps_; ++step_) { + if (mpi_rank == 0) console_->info("Step: {} of {}.\n", step_, nsteps_); + +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + // Run load balancer at a specified frequency + if (step_ % nload_balance_steps_ == 0 && step_ != 0) + this->mpi_domain_decompose(false); +#endif +#endif + +#pragma omp parallel sections + { + // Spawn a task for initialising nodes and cells +#pragma omp section + { + // Initialise nodes + mesh_->iterate_over_nodes( + std::bind(&mpm::NodeBase::initialise, std::placeholders::_1)); + + mesh_->iterate_over_cells( + std::bind(&mpm::Cell::activate_nodes, std::placeholders::_1)); + } + // Spawn a task for particles +#pragma omp section + { + // Iterate over each particle to compute shapefn + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_shapefn, std::placeholders::_1)); + } + } // Wait to complete + + // Assign mass and momentum to nodes + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_mass_momentum_to_nodes, + std::placeholders::_1)); + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce nodal mass + mesh_->template nodal_halo_exchange( + std::bind(&mpm::NodeBase::mass, std::placeholders::_1, fluid), + std::bind(&mpm::NodeBase::update_mass, std::placeholders::_1, + false, fluid, std::placeholders::_2)); + // MPI all reduce nodal momentum + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::momentum, std::placeholders::_1, + fluid), + std::bind(&mpm::NodeBase::update_momentum, + std::placeholders::_1, false, fluid, + std::placeholders::_2)); + } +#endif + + // Compute free surface cells, nodes, and particles + mesh_->compute_free_surface(free_surface_detection_, volume_tolerance_); + + // Spawn a task for initializing pressure at free surface +#pragma omp parallel sections + { +#pragma omp section + { + // Assign initial pressure for all free-surface particle + mesh_->iterate_over_particles_predicate( + std::bind(&mpm::ParticleBase::assign_pressure, + std::placeholders::_1, 0.0, fluid), + std::bind(&mpm::ParticleBase::free_surface, + std::placeholders::_1)); + } + } // Wait to complete + + // Compute nodal velocity at the begining of time step + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::compute_velocity, + std::placeholders::_1), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Iterate over each particle to compute strain rate + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_strain, std::placeholders::_1, dt_)); + + // Iterate over each particle to compute shear (deviatoric) stress + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_stress, std::placeholders::_1)); + + // Spawn a task for external force +#pragma omp parallel sections + { +#pragma omp section + { + // Iterate over particles to compute nodal body force + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_body_force, + std::placeholders::_1, this->gravity_)); + + // Apply particle traction and map to nodes + mesh_->apply_traction_on_particles(this->step_ * this->dt_); + } + +#pragma omp section + { + // Spawn a task for internal force + // Iterate over each particle to compute nodal internal force + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_internal_force, + std::placeholders::_1)); + } + } // Wait for tasks to finish + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce external force + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::external_force, std::placeholders::_1, + fluid), + std::bind(&mpm::NodeBase::update_external_force, + std::placeholders::_1, false, fluid, + std::placeholders::_2)); + // MPI all reduce internal force + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::internal_force, std::placeholders::_1, + fluid), + std::bind(&mpm::NodeBase::update_internal_force, + std::placeholders::_1, false, fluid, + std::placeholders::_2)); + } +#endif + + // Compute intermediate velocity + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::compute_acceleration_velocity, + std::placeholders::_1, fluid, this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Reinitialise system matrix to perform PPE + bool matrix_reinitialization_status = this->reinitialise_matrix(); + if (!matrix_reinitialization_status) { + status = false; + throw std::runtime_error("Reinitialisation of matrix failed"); + } + + // Compute poisson equation + this->compute_poisson_equation(); + + // Assign pressure to nodes + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::update_pressure_increment, + std::placeholders::_1, assembler_->pressure_increment(), + fluid, this->step_ * this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Use nodal pressure to update particle pressure + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::compute_updated_pressure, + std::placeholders::_1)); + + // Compute correction force + this->compute_correction_force(); + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce correction force + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::correction_force, + std::placeholders::_1, fluid), + std::bind(&mpm::NodeBase::update_correction_force, + std::placeholders::_1, false, fluid, + std::placeholders::_2)); + } +#endif + + // Compute corrected acceleration and velocity + mesh_->iterate_over_nodes_predicate( + std::bind( + &mpm::NodeBase< + Tdim>::compute_acceleration_velocity_semi_implicit_corrector, + std::placeholders::_1, fluid, this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Update particle position and kinematics + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::compute_updated_position, + std::placeholders::_1, this->dt_, velocity_update_)); + + // Apply particle velocity constraints + mesh_->apply_particle_velocity_constraints(); + + // Pressure smoothing + if (pressure_smoothing_) this->pressure_smoothing(fluid); + + // Locate particle + auto unlocatable_particles = mesh_->locate_particles_mesh(); + + if (!unlocatable_particles.empty() && this->locate_particles_) + throw std::runtime_error("Particle outside the mesh domain"); + // If unable to locate particles remove particles + if (!unlocatable_particles.empty() && !this->locate_particles_) + for (const auto& remove_particle : unlocatable_particles) + mesh_->remove_particle(remove_particle); + +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + mesh_->transfer_halo_particles(); + MPI_Barrier(MPI_COMM_WORLD); +#endif +#endif + + if (step_ % output_steps_ == 0) { + // HDF5 outputs + this->write_hdf5(this->step_, this->nsteps_); +#ifdef USE_VTK + // VTK outputs + this->write_vtk(this->step_, this->nsteps_); +#endif + } + } + auto solver_end = std::chrono::steady_clock::now(); + console_->info("Rank {}, SemiImplicit_NavierStokes solver duration: {} ms", + mpi_rank, + std::chrono::duration_cast( + solver_end - solver_begin) + .count()); + + return status; +} + +// Semi-implicit functions +// Initialise matrix +template +bool mpm::MPMSemiImplicitNavierStokes::initialise_matrix() { + bool status = true; + try { + // Get matrix assembler type + std::string assembler_type = analysis_["linear_solver"]["assembler_type"] + .template get(); + // Create matrix assembler + assembler_ = + Factory, unsigned>::instance()->create( + assembler_type, std::move(node_neighbourhood_)); + + // Solver settings + if (analysis_["linear_solver"].contains("solver_settings") && + analysis_["linear_solver"].at("solver_settings").is_array() && + analysis_["linear_solver"].at("solver_settings").size() > 0) { + mpm::MPMBase::initialise_linear_solver( + analysis_["linear_solver"]["solver_settings"], linear_solver_); + } + // Default solver settings + else { + std::string solver_type = "IterativeEigen"; + unsigned max_iter = 1000; + double tolerance = 1.E-7; + + // In case the default settings are specified in json + if (analysis_["linear_solver"].contains("solver_type")) { + solver_type = analysis_["linear_solver"]["solver_type"] + .template get(); + } + // Max iteration steps + if (analysis_["linear_solver"].contains("max_iter")) { + max_iter = + analysis_["linear_solver"]["max_iter"].template get(); + } + // Tolerance + if (analysis_["linear_solver"].contains("tolerance")) { + tolerance = + analysis_["linear_solver"]["tolerance"].template get(); + } + + // NOTE: Only KrylovPETSC solver is supported for MPI +#ifdef USE_MPI + // Get number of MPI ranks + int mpi_size = 1; + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); + + if (solver_type != "KrylovPETSC" && mpi_size > 1) { + console_->warn( + "The linear solver in MPI setting is automatically set to default: " + "\'KrylovPETSC\'. Only \'KrylovPETSC\' solver is supported for " + "MPI."); + solver_type = "KrylovPETSC"; + } +#endif + + // Create matrix solver + auto lin_solver = + Factory>, unsigned, + double>::instance() + ->create(solver_type, std::move(max_iter), std::move(tolerance)); + // Add solver set to map + linear_solver_.insert( + std::pair< + std::string, + std::shared_ptr>>>( + "pressure", lin_solver)); + } + + // Assign mesh pointer to assembler + assembler_->assign_mesh_pointer(mesh_); + + // Get method to detect free surface detection + free_surface_detection_ = "density"; + if (analysis_["free_surface_detection"].contains("type")) + free_surface_detection_ = analysis_["free_surface_detection"]["type"] + .template get(); + // Get volume tolerance for free surface + volume_tolerance_ = analysis_["free_surface_detection"]["volume_tolerance"] + .template get(); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Reinitialise and resize matrices at the beginning of every time step +template +bool mpm::MPMSemiImplicitNavierStokes::reinitialise_matrix() { + + bool status = true; + try { + // Assigning matrix id (in each MPI rank) + const auto nactive_node = mesh_->assign_active_nodes_id(); + + // Assigning matrix id globally (required for rank-to-global mapping) + unsigned nglobal_active_node = nactive_node; +#ifdef USE_MPI + nglobal_active_node = mesh_->assign_global_active_nodes_id(); +#endif + + // Assign global node indice + assembler_->assign_global_node_indices(nactive_node, nglobal_active_node); + + // Assign pressure constraints + assembler_->assign_pressure_constraints(this->beta_, + this->step_ * this->dt_); + + // Initialise element matrix + mesh_->iterate_over_cells(std::bind( + &mpm::Cell::initialise_element_matrix, std::placeholders::_1)); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Compute poisson equation +template +bool mpm::MPMSemiImplicitNavierStokes::compute_poisson_equation() { + bool status = true; + try { + // Construct local cell laplacian matrix + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_laplacian_to_cell, + std::placeholders::_1)); + + // Assemble global laplacian matrix + assembler_->assemble_laplacian_matrix(dt_); + + // Map Poisson RHS matrix + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_poisson_right_to_cell, + std::placeholders::_1)); + + // Assemble poisson RHS vector + assembler_->assemble_poisson_right(dt_); + + // Assign free surface to assembler + assembler_->assign_free_surface(mesh_->free_surface_nodes()); + + // Apply constraints + assembler_->apply_pressure_constraints(); + +#ifdef USE_MPI + // Assign global active dof to solver + linear_solver_["pressure"]->assign_global_active_dof( + assembler_->global_active_dof()); + + // Assign rank global mapper to solver + linear_solver_["pressure"]->assign_rank_global_mapper( + assembler_->rank_global_mapper()); +#endif + + // Solve matrix equation and assign solution to assembler + assembler_->assign_pressure_increment(linear_solver_["pressure"]->solve( + assembler_->laplacian_matrix(), assembler_->poisson_rhs_vector())); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Compute correction force +template +bool mpm::MPMSemiImplicitNavierStokes::compute_correction_force() { + bool status = true; + try { + // Map correction matrix from particles to cell + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_correction_matrix_to_cell, + std::placeholders::_1)); + + // Assemble correction matrix + assembler_->assemble_corrector_right(dt_); + + // Assign correction force + mesh_->compute_nodal_correction_force( + assembler_->correction_matrix(), assembler_->pressure_increment(), dt_); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} diff --git a/include/solvers/mpm_semi_implicit_twophase.h b/include/solvers/mpm_semi_implicit_twophase.h new file mode 100644 index 000000000..0fdd0124f --- /dev/null +++ b/include/solvers/mpm_semi_implicit_twophase.h @@ -0,0 +1,114 @@ +#ifndef MPM_MPM_SEMI_IMPLICIT_TWOPHASE_H_ +#define MPM_MPM_SEMI_IMPLICIT_TWOPHASE_H_ + +#ifdef USE_GRAPH_PARTITIONING +#include "graph.h" +#endif + +#include "mpm_base.h" + +#include "assembler_base.h" +#include "solver_base.h" + +namespace mpm { + +//! MPMSemiImplicit Two Phase class +//! \brief A class that implements the fractional step two-phase mpm +//! \tparam Tdim Dimension +template +class MPMSemiImplicitTwoPhase : public MPMBase { + public: + //! Default constructor + MPMSemiImplicitTwoPhase(const std::shared_ptr& io); + + //! Return matrix assembler pointer + std::shared_ptr> matrix_assembler() { + return assembler_; + } + + //! Compute stress strain + void compute_stress_strain(); + + //! Solve + bool solve() override; + + //! Class private functions + private: + //! Initialise matrix + bool initialise_matrix(); + + //! Initialise matrix + bool reinitialise_matrix(); + + //! Compute intermediate acceleration and velocity + bool compute_intermediate_acceleration_velocity(); + + //! Compute poisson equation + bool compute_poisson_equation(); + + //! Compute corrected velocity + bool compute_correction_force(); + + //! Class private variables + private: + // Generate a unique id for the analysis + using mpm::MPMBase::uuid_; + //! Time step size + using mpm::MPMBase::dt_; + //! Current step + using mpm::MPMBase::step_; + //! Number of steps + using mpm::MPMBase::nsteps_; + //! Number of steps + using mpm::MPMBase::nload_balance_steps_; + //! Output steps + using mpm::MPMBase::output_steps_; + //! A unique ptr to IO object + using mpm::MPMBase::io_; + //! JSON analysis object + using mpm::MPMBase::analysis_; + //! JSON post-process object + using mpm::MPMBase::post_process_; + //! Logger + using mpm::MPMBase::console_; + //! Damping type + using mpm::MPMBase::damping_type_; + //! Damping factor + using mpm::MPMBase::damping_factor_; + //! Stress update + using mpm::MPMBase::stress_update_; + //! velocity update + using mpm::MPMBase::velocity_update_; + //! Gravity + using mpm::MPMBase::gravity_; + //! Mesh object + using mpm::MPMBase::mesh_; + //! Materials + using mpm::MPMBase::materials_; + //! Nonlocal neighbourhood + using mpm::MPMBase::node_neighbourhood_; + //! Node concentrated force + using mpm::MPMBase::set_node_concentrated_force_; + //! Pressure smoothing + bool pressure_smoothing_{false}; + //! Pore pressure smoothing + bool pore_pressure_smoothing_{false}; + // Projection method parameter (beta) + double beta_{1}; + //! Assembler object + std::shared_ptr> assembler_; + //! Linear solver object + tsl::robin_map>>> + linear_solver_; + //! Method to detect free surface detection + std::string free_surface_detection_; + //! Volume tolerance for free surface + double volume_tolerance_{0}; + +}; // MPMSemiImplicit class +} // namespace mpm + +#include "mpm_semi_implicit_twophase.tcc" + +#endif // MPM_MPM_SEMI_IMPLICIT_TWOPHASE_H_ diff --git a/include/solvers/mpm_semi_implicit_twophase.tcc b/include/solvers/mpm_semi_implicit_twophase.tcc new file mode 100644 index 000000000..9ea29c5ee --- /dev/null +++ b/include/solvers/mpm_semi_implicit_twophase.tcc @@ -0,0 +1,727 @@ +//! Constructor +template +mpm::MPMSemiImplicitTwoPhase::MPMSemiImplicitTwoPhase( + const std::shared_ptr& io) + : mpm::MPMBase(io) { + //! Logger + console_ = spdlog::get("MPMSemiImplicitTwoPhase"); +} + +//! MPM Semi-implicit TwoPhase compute stress strain +template +void mpm::MPMSemiImplicitTwoPhase::compute_stress_strain() { + // Iterate over each particle to calculate strain of soil_skeleton + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_strain, std::placeholders::_1, dt_)); + // Iterate over each particle to update particle volume + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::update_volume, std::placeholders::_1)); + // Iterate over each particle to update porosity + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::update_porosity, std::placeholders::_1, dt_)); + // Iterate over each particle to compute stress of soil skeleton + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_stress, std::placeholders::_1)); + // Pressure smoothing + if (pressure_smoothing_) this->pressure_smoothing(mpm::ParticlePhase::Solid); + // Pore pressure smoothing + if (pore_pressure_smoothing_) { + this->pressure_smoothing(mpm::ParticlePhase::Liquid); + } +} + +//! MPM semi-implicit two-phase solver +template +bool mpm::MPMSemiImplicitTwoPhase::solve() { + bool status = true; + + console_->info("MPM analysis type {}", io_->analysis_type()); + + // Initialise MPI rank and size + int mpi_rank = 0; + int mpi_size = 1; + +#ifdef USE_MPI + // Get MPI rank + MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); + // Get number of MPI ranks + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); +#endif + + // Test if checkpoint resume is needed + bool resume = false; + if (analysis_.find("resume") != analysis_.end()) + resume = analysis_["resume"]["resume"].template get(); + + // Pressure smoothing + if (analysis_.find("pressure_smoothing") != analysis_.end()) + pressure_smoothing_ = analysis_["pressure_smoothing"].template get(); + + // Pore pressure smoothing + if (analysis_.find("pore_pressure_smoothing") != analysis_.end()) + pore_pressure_smoothing_ = + analysis_["pore_pressure_smoothing"].template get(); + + // Projection method parameter (beta) + if (analysis_.find("semi_implicit") != analysis_.end()) + beta_ = analysis_["semi_implicit"]["beta"].template get(); + + // Initialise material + this->initialise_materials(); + + // Initialise mesh + this->initialise_mesh(); + + // Check point resume + if (resume) { + bool check_resume = this->checkpoint_resume(); + if (!check_resume) resume = false; + } + + // Check point resume + if (resume) { + mesh_->resume_domain_cell_ranks(); +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + MPI_Barrier(MPI_COMM_WORLD); +#endif +#endif + + //! Particle entity sets and velocity constraints + this->particle_entity_sets(false); + this->particle_velocity_constraints(); + } else { + // Initialise particles + this->initialise_particles(); + + // Assign porosity + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::assign_porosity, std::placeholders::_1)); + + // Assign permeability + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::assign_permeability, std::placeholders::_1)); + + // Compute mass for each phase + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_mass, std::placeholders::_1)); + + // Domain decompose + bool initial_step = (resume == true) ? false : true; + this->mpi_domain_decompose(initial_step); + } + + // Initialise loading conditions + this->initialise_loads(); + + // Initialise matrix + bool matrix_status = this->initialise_matrix(); + if (!matrix_status) { + status = false; + throw std::runtime_error("Initialisation of matrix failed"); + } + + // Assign beta to each particle + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::assign_projection_parameter, + std::placeholders::_1, beta_)); + + auto solver_begin = std::chrono::steady_clock::now(); + // Main loop + for (; step_ < nsteps_; ++step_) { + if (mpi_rank == 0) console_->info("Step: {} of {}.\n", step_, nsteps_); + +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + // Run load balancer at a specified frequency + if (step_ % nload_balance_steps_ == 0 && step_ != 0) + this->mpi_domain_decompose(false); +#endif +#endif + +#pragma omp parallel sections + { + // Spawn a task for initialising nodes and cells +#pragma omp section + { + // Initialise nodes + mesh_->iterate_over_nodes(std::bind( + &mpm::NodeBase::initialise_twophase, std::placeholders::_1)); + + mesh_->iterate_over_cells( + std::bind(&mpm::Cell::activate_nodes, std::placeholders::_1)); + } + // Spawn a task for particles +#pragma omp section + { + // Iterate over each particle to compute shapefn + mesh_->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_shapefn, std::placeholders::_1)); + } + } // Wait to complete + + // Assign mass and momentum to nodes + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_mass_momentum_to_nodes, + std::placeholders::_1)); + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce nodal mass for solid phase + mesh_->template nodal_halo_exchange( + std::bind(&mpm::NodeBase::mass, std::placeholders::_1, + mpm::NodePhase::NSolid), + std::bind(&mpm::NodeBase::update_mass, std::placeholders::_1, + false, mpm::NodePhase::NSolid, std::placeholders::_2)); + // MPI all reduce nodal momentum for solid phase + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::momentum, std::placeholders::_1, + mpm::NodePhase::NSolid), + std::bind(&mpm::NodeBase::update_momentum, + std::placeholders::_1, false, mpm::NodePhase::NSolid, + std::placeholders::_2)); + + // MPI all reduce nodal mass for liquid phase + mesh_->template nodal_halo_exchange( + std::bind(&mpm::NodeBase::mass, std::placeholders::_1, + mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_mass, std::placeholders::_1, + false, mpm::NodePhase::NLiquid, std::placeholders::_2)); + // MPI all reduce nodal momentum for liquid phase + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::momentum, std::placeholders::_1, + mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_momentum, + std::placeholders::_1, false, mpm::NodePhase::NLiquid, + std::placeholders::_2)); + } +#endif + + // Compute free surface cells, nodes, and particles + mesh_->compute_free_surface(free_surface_detection_, volume_tolerance_); + + // Spawn a task for initializing pressure at free surface +#pragma omp parallel sections + { +#pragma omp section + { + // Assign initial pressure for all free-surface particle + mesh_->iterate_over_particles_predicate( + std::bind(&mpm::ParticleBase::assign_pressure, + std::placeholders::_1, 0.0, mpm::ParticlePhase::Liquid), + std::bind(&mpm::ParticleBase::free_surface, + std::placeholders::_1)); + } + } // Wait to complete + + // Compute nodal velocity at the begining of time step + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::compute_velocity, + std::placeholders::_1), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Update stress first + if (this->stress_update_ == "usf") this->compute_stress_strain(); + + // Spawn a task for external force +#pragma omp parallel sections + { +#pragma omp section + { + // Iterate over particles to compute nodal body force + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_body_force, + std::placeholders::_1, this->gravity_)); + + // Apply particle traction and map to nodes + mesh_->apply_traction_on_particles(this->step_ * this->dt_); + + // Iterate over each node to add concentrated node force to external + // force + if (set_node_concentrated_force_) + mesh_->iterate_over_nodes( + std::bind(&mpm::NodeBase::apply_concentrated_force, + std::placeholders::_1, mpm::ParticlePhase::Solid, + (this->step_ * this->dt_))); + } + +#pragma omp section + { + // Spawn a task for internal force + // Iterate over each particle to compute nodal internal force + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_internal_force, + std::placeholders::_1)); + } + +#pragma omp section + { + // Iterate over particles to compute nodal drag force coefficient + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_drag_force_coefficient, + std::placeholders::_1)); + } + } // Wait for tasks to finish + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce external force of mixture + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::external_force, std::placeholders::_1, + mpm::NodePhase::NMixture), + std::bind(&mpm::NodeBase::update_external_force, + std::placeholders::_1, false, mpm::NodePhase::NMixture, + std::placeholders::_2)); + // MPI all reduce external force of pore fluid + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::external_force, std::placeholders::_1, + mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_external_force, + std::placeholders::_1, false, mpm::NodePhase::NLiquid, + std::placeholders::_2)); + + // MPI all reduce internal force of mixture + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::internal_force, std::placeholders::_1, + mpm::NodePhase::NMixture), + std::bind(&mpm::NodeBase::update_internal_force, + std::placeholders::_1, false, mpm::NodePhase::NMixture, + std::placeholders::_2)); + // MPI all reduce internal force of pore liquid + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::internal_force, std::placeholders::_1, + mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_internal_force, + std::placeholders::_1, false, mpm::NodePhase::NLiquid, + std::placeholders::_2)); + + // MPI all reduce drag force + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::drag_force_coefficient, + std::placeholders::_1), + std::bind(&mpm::NodeBase::update_drag_force_coefficient, + std::placeholders::_1, false, std::placeholders::_2)); + } +#endif + + // Reinitialise system matrices to solve predictor equation and PPE + bool matrix_reinitialization_status = this->reinitialise_matrix(); + if (!matrix_reinitialization_status) { + status = false; + throw std::runtime_error("Reinitialisation of matrix failed"); + } + + // Compute intermediate acceleration and velocity + this->compute_intermediate_acceleration_velocity(); + + // Compute poisson equation + this->compute_poisson_equation(); + + // Assign pressure to nodes + mesh_->iterate_over_nodes_predicate( + std::bind(&mpm::NodeBase::update_pressure_increment, + std::placeholders::_1, assembler_->pressure_increment(), + mpm::NodePhase::NLiquid, this->step_ * this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Use nodal pressure to update particle pressure + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::compute_updated_pressure, + std::placeholders::_1)); + + // Compute correction force + this->compute_correction_force(); + +#ifdef USE_MPI + // Run if there is more than a single MPI task + if (mpi_size > 1) { + // MPI all reduce correction force for solid phase + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::correction_force, + std::placeholders::_1, mpm::NodePhase::NSolid), + std::bind(&mpm::NodeBase::update_correction_force, + std::placeholders::_1, false, mpm::NodePhase::NSolid, + std::placeholders::_2)); + // MPI all reduce correction force for liquid phase + mesh_->template nodal_halo_exchange, Tdim>( + std::bind(&mpm::NodeBase::correction_force, + std::placeholders::_1, mpm::NodePhase::NLiquid), + std::bind(&mpm::NodeBase::update_correction_force, + std::placeholders::_1, false, mpm::NodePhase::NLiquid, + std::placeholders::_2)); + } +#endif + + if (damping_type_ == mpm::Damping::Cundall) { + // Iterate over active nodes to compute acceleratation and velocity + mesh_->iterate_over_nodes_predicate( + std::bind( + &mpm::NodeBase:: + compute_acceleration_velocity_semi_implicit_corrector_cundall, + std::placeholders::_1, mpm::NodePhase::NSolid, this->dt_, + damping_factor_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + mesh_->iterate_over_nodes_predicate( + std::bind( + &mpm::NodeBase:: + compute_acceleration_velocity_semi_implicit_corrector_cundall, + std::placeholders::_1, mpm::NodePhase::NLiquid, this->dt_, + damping_factor_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + } else { + // Iterate over active nodes to compute acceleratation and velocity + mesh_->iterate_over_nodes_predicate( + std::bind( + &mpm::NodeBase< + Tdim>::compute_acceleration_velocity_semi_implicit_corrector, + std::placeholders::_1, mpm::NodePhase::NSolid, this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + mesh_->iterate_over_nodes_predicate( + std::bind( + &mpm::NodeBase< + Tdim>::compute_acceleration_velocity_semi_implicit_corrector, + std::placeholders::_1, mpm::NodePhase::NLiquid, this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + } + + // Update particle position and kinematics + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::compute_updated_position, + std::placeholders::_1, this->dt_, velocity_update_)); + + // Apply particle velocity constraints + mesh_->apply_particle_velocity_constraints(); + + // Update stress first + if (this->stress_update_ == "usl") this->compute_stress_strain(); + + // Locate particle + auto unlocatable_particles = mesh_->locate_particles_mesh(); + + if (!unlocatable_particles.empty() && this->locate_particles_) + throw std::runtime_error("Particle outside the mesh domain"); + + // If unable to locate particles remove particles + if (!unlocatable_particles.empty() && !this->locate_particles_) + for (const auto& remove_particle : unlocatable_particles) + mesh_->remove_particle(remove_particle); + +#ifdef USE_MPI +#ifdef USE_GRAPH_PARTITIONING + mesh_->transfer_halo_particles(); + MPI_Barrier(MPI_COMM_WORLD); +#endif +#endif + + if (step_ % output_steps_ == 0) { + // HDF5 outputs + this->write_hdf5_twophase(this->step_, this->nsteps_); +#ifdef USE_VTK + // VTK outputs + this->write_vtk(this->step_, this->nsteps_); +#endif + } + } + auto solver_end = std::chrono::steady_clock::now(); + console_->info("Rank {}, SemiImplicit TwoPhase solver duration: {} ms", + mpi_rank, + std::chrono::duration_cast( + solver_end - solver_begin) + .count()); + + return status; +} + +// Semi-implicit functions +// Initialise matrix +template +bool mpm::MPMSemiImplicitTwoPhase::initialise_matrix() { + bool status = true; + try { + // Get matrix assembler type + std::string assembler_type = analysis_["linear_solver"]["assembler_type"] + .template get(); + // Create matrix assembler + assembler_ = + Factory, unsigned>::instance()->create( + assembler_type, std::move(node_neighbourhood_)); + + // Solver settings + if (analysis_["linear_solver"].contains("solver_settings") && + analysis_["linear_solver"].at("solver_settings").is_array() && + analysis_["linear_solver"].at("solver_settings").size() > 0) { + mpm::MPMBase::initialise_linear_solver( + analysis_["linear_solver"]["solver_settings"], linear_solver_); + } + // Default solver settings + else { + std::string solver_type = "IterativeEigen"; + unsigned max_iter = 1000; + double tolerance = 1.E-7; + std::string sub_solver_type_pressure = "cg"; + std::string sub_solver_type_acc = "lscg"; + + // In case the default settings are specified in json + if (analysis_["linear_solver"].contains("solver_type")) { + solver_type = analysis_["linear_solver"]["solver_type"] + .template get(); + } + // Max iteration steps + if (analysis_["linear_solver"].contains("max_iter")) { + max_iter = + analysis_["linear_solver"]["max_iter"].template get(); + } + // Tolerance + if (analysis_["linear_solver"].contains("tolerance")) { + tolerance = + analysis_["linear_solver"]["tolerance"].template get(); + } + + // NOTE: Only KrylovPETSC solver is supported for MPI +#ifdef USE_MPI + // Get number of MPI ranks + int mpi_size = 1; + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); + + if (solver_type != "KrylovPETSC" && mpi_size > 1) { + console_->warn( + "The linear solver in MPI setting is automatically set to default: " + "\'KrylovPETSC\'. Only \'KrylovPETSC\' solver is supported for " + "MPI."); + solver_type = "KrylovPETSC"; + } +#endif + + // If "KrylovPETSC" is selected, modify lscg to lsqr for solving acc + if (solver_type == "KrylovPETSC") sub_solver_type_acc = "lsqr"; + + // Create matrix solver for pressure + auto press_lin_solver = + Factory>, unsigned, + double>::instance() + ->create(solver_type, std::move(max_iter), std::move(tolerance)); + press_lin_solver->set_sub_solver_type(sub_solver_type_pressure); + linear_solver_.insert( + std::pair< + std::string, + std::shared_ptr>>>( + "pressure", press_lin_solver)); + + // Create matrix solver for acceleration + auto acc_lin_solver = + Factory>, unsigned, + double>::instance() + ->create(solver_type, std::move(max_iter), std::move(tolerance)); + acc_lin_solver->set_sub_solver_type(sub_solver_type_acc); + linear_solver_.insert( + std::pair< + std::string, + std::shared_ptr>>>( + "acceleration", acc_lin_solver)); + } + + // Assign mesh pointer to assembler + assembler_->assign_mesh_pointer(mesh_); + + // Get method to detect free surface detection + free_surface_detection_ = "density"; + if (analysis_["free_surface_detection"].contains("type")) + free_surface_detection_ = analysis_["free_surface_detection"]["type"] + .template get(); + // Get volume tolerance for free surface + volume_tolerance_ = analysis_["free_surface_detection"]["volume_tolerance"] + .template get(); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Reinitialise and resize matrices at the beginning of every time step +template +bool mpm::MPMSemiImplicitTwoPhase::reinitialise_matrix() { + + bool status = true; + try { + // Assigning matrix id (in each MPI rank) + const auto nactive_node = mesh_->assign_active_nodes_id(); + + // Assigning matrix id globally (required for rank-to-global mapping) + unsigned nglobal_active_node = nactive_node; +#ifdef USE_MPI + nglobal_active_node = mesh_->assign_global_active_nodes_id(); +#endif + + // Assign global node indice + assembler_->assign_global_node_indices(nactive_node, nglobal_active_node); + + // Assign pressure constraints + assembler_->assign_pressure_constraints(this->beta_, + this->step_ * this->dt_); + + // Assign velocity constraints + assembler_->assign_velocity_constraints(); + + // Initialise element matrix + mesh_->iterate_over_cells( + std::bind(&mpm::Cell::initialise_element_matrix_twophase, + std::placeholders::_1)); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Compute intermediate velocity +template +bool mpm::MPMSemiImplicitTwoPhase< + Tdim>::compute_intermediate_acceleration_velocity() { + bool status = true; + try { + // Map coupling drag matrix to cell + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_drag_matrix_to_cell, + std::placeholders::_1)); + + // Assemble coefficient matrix LHS for each direction + assembler_->assemble_predictor_left(dt_); + + // Assemble predictor RHS force vector + assembler_->assemble_predictor_right(dt_); + + // Apply velocity constraints to predictor LHS and RHS + assembler_->apply_velocity_constraints(); + +#ifdef USE_MPI + // Assign global active dof to solver + linear_solver_["acceleration"]->assign_global_active_dof( + 2 * assembler_->global_active_dof()); + + // Prepare rank global mapper to compute predictor equation + auto predictor_rgm = assembler_->rank_global_mapper(); + auto predictor_rgm_liquid = assembler_->rank_global_mapper(); + std::for_each( + predictor_rgm_liquid.begin(), predictor_rgm_liquid.end(), + [size = assembler_->global_active_dof()](int& rgm) { rgm += size; }); + predictor_rgm.insert(predictor_rgm.end(), predictor_rgm_liquid.begin(), + predictor_rgm_liquid.end()); + + // Assign rank global mapper to solver + linear_solver_["acceleration"]->assign_rank_global_mapper(predictor_rgm); +#endif + + // Compute matrix equation of each direction + for (unsigned dir = 0; dir < Tdim; ++dir) { + // Solve equation 1 to compute intermediate acceleration + assembler_->assign_intermediate_acceleration( + dir, linear_solver_["acceleration"]->solve( + assembler_->predictor_lhs_matrix(dir), + assembler_->predictor_rhs_vector().col(dir))); + } + + // Update intermediate acceleration and velocity of solid phase + mesh_->iterate_over_nodes_predicate( + std::bind( + &mpm::NodeBase::update_intermediate_acceleration_velocity, + std::placeholders::_1, mpm::NodePhase::NSolid, + assembler_->intermediate_acceleration().topRows( + assembler_->active_dof()), + this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + // Update intermediate acceleration and velocity of water phase + mesh_->iterate_over_nodes_predicate( + std::bind( + &mpm::NodeBase::update_intermediate_acceleration_velocity, + std::placeholders::_1, mpm::NodePhase::NLiquid, + assembler_->intermediate_acceleration().bottomRows( + assembler_->active_dof()), + this->dt_), + std::bind(&mpm::NodeBase::status, std::placeholders::_1)); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +// Compute poisson equation +template +bool mpm::MPMSemiImplicitTwoPhase::compute_poisson_equation() { + bool status = true; + try { + // Construct local cell laplacian matrix + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_laplacian_to_cell, + std::placeholders::_1)); + + // Assemble global laplacian matrix + assembler_->assemble_laplacian_matrix(dt_); + + // Map Poisson RHS matrix + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_poisson_right_to_cell, + std::placeholders::_1)); + + // Assemble poisson RHS vector + assembler_->assemble_poisson_right(dt_); + + // Assign free surface to assembler + assembler_->assign_free_surface(mesh_->free_surface_nodes()); + + // Apply constraints + assembler_->apply_pressure_constraints(); + +#ifdef USE_MPI + // Assign global active dof to solver + linear_solver_["pressure"]->assign_global_active_dof( + assembler_->global_active_dof()); + + // Assign rank global mapper to solver + linear_solver_["pressure"]->assign_rank_global_mapper( + assembler_->rank_global_mapper()); +#endif + + // Solve matrix equation and assign solution to assembler + assembler_->assign_pressure_increment(linear_solver_["pressure"]->solve( + assembler_->laplacian_matrix(), assembler_->poisson_rhs_vector())); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} + +//! Compute correction force +template +bool mpm::MPMSemiImplicitTwoPhase::compute_correction_force() { + bool status = true; + try { + // Map correction matrix from particles to cell + mesh_->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_correction_matrix_to_cell, + std::placeholders::_1)); + + // Assemble correction matrix + assembler_->assemble_corrector_right(dt_); + + // Assign correction force + mesh_->compute_nodal_correction_force_twophase( + assembler_->correction_matrix(), assembler_->pressure_increment(), dt_); + + } catch (std::exception& exception) { + console_->error("{} #{}: {}\n", __FILE__, __LINE__, exception.what()); + status = false; + } + return status; +} diff --git a/include/utilities/radial_basis_function.h b/include/utilities/radial_basis_function.h new file mode 100644 index 000000000..134bca5ca --- /dev/null +++ b/include/utilities/radial_basis_function.h @@ -0,0 +1,300 @@ +#ifndef MPM_RADIAL_BASIS_FUNCTION_H_ +#define MPM_RADIAL_BASIS_FUNCTION_H_ + +#include + +#include "Eigen/Dense" + +#include "logger.h" + +namespace mpm { + +// Namespace for radial basis function handling +// NOTES: only accessible through function kernel() and gradient() +namespace RadialBasisFunction { +// Private functions +namespace { + +//! Cubic Spline Radial Basis Function +//! Source: Monaghan, 1985; Monaghan, 1992 +template +double cubic_spline(double smoothing_length, double norm_distance) { + + // Assign multiplier depends on dimension + double multiplier; + if (Tdim == 2) + multiplier = 15.0 / (7.0 * M_PI * std::pow(smoothing_length, 2)); + else if (Tdim == 3) + multiplier = 3.0 / (2.0 * M_PI * std::pow(smoothing_length, 3)); + else + throw std::runtime_error("Tdim is invalid"); + + // Compute basis function + double basis_function = multiplier; + const double radius = norm_distance / smoothing_length; + if (radius >= 0.0 && radius < 1.0) + basis_function *= + (2.0 / 3.0 - std::pow(radius, 2) + 0.5 * std::pow(radius, 3)); + else if (radius >= 1.0 && radius < 2.0) + basis_function *= (1.0 / 6.0 * std::pow((2.0 - radius), 3)); + else + basis_function = 0.0; + + return basis_function; +} + +//! Cubic Spline Radial Basis Function derivative +//! Source: Monaghan, 1985; Monaghan, 1992 +template +double cubic_spline_derivative(double smoothing_length, double norm_distance) { + + // Assign multiplier depends on dimension + double multiplier; + if (Tdim == 2) + multiplier = 15.0 / (7.0 * M_PI * std::pow(smoothing_length, 2)); + else if (Tdim == 3) + multiplier = 3.0 / (2.0 * M_PI * std::pow(smoothing_length, 3)); + else + throw std::runtime_error("Tdim is invalid"); + + // Compute basis function derivative + double dw_dr = multiplier; + const double radius = norm_distance / smoothing_length; + if (radius >= 0.0 && radius < 1.0) + dw_dr *= (-2.0 * radius + 1.5 * std::pow(radius, 2)); + else if (radius >= 1.0 && radius < 2.0) + dw_dr *= -0.5 * std::pow((2.0 - radius), 2); + else + dw_dr = 0.0; + + return dw_dr; +} + +//! Quintic Spline Radial Basis Function +//! Source: Liu, 2010 +template +double quintic_spline(double smoothing_length, double norm_distance) { + + // Assign multiplier depends on dimension + double multiplier; + if (Tdim == 2) + multiplier = 1.0 / (478.0 * M_PI * std::pow(smoothing_length, 2)); + else if (Tdim == 3) + multiplier = 3.0 / (359.0 * M_PI * std::pow(smoothing_length, 3)); + else + throw std::runtime_error("Tdim is invalid"); + + // Compute basis function + double basis_function = multiplier; + const double radius = norm_distance / smoothing_length; + if (radius >= 0.0 && radius < 1.0) + basis_function *= + (std::pow(3.0 - radius, 5) - 6.0 * std::pow(2.0 - radius, 5) + + 15.0 * std::pow(1.0 - radius, 5)); + else if (radius >= 1.0 && radius < 2.0) + basis_function *= + (std::pow(3.0 - radius, 5) - 6.0 * std::pow(2.0 - radius, 5)); + else if (radius >= 2.0 && radius < 3.0) + basis_function *= (std::pow(3.0 - radius, 5)); + else + basis_function = 0.0; + + return basis_function; +} + +//! Quintic Spline Radial Basis Function derivative +//! Source: Liu, 2010 +template +double quintic_spline_derivative(double smoothing_length, + double norm_distance) { + + // Assign multiplier depends on dimension + double multiplier; + if (Tdim == 2) + multiplier = 1.0 / (478.0 * M_PI * std::pow(smoothing_length, 2)); + else if (Tdim == 3) + multiplier = 3.0 / (359.0 * M_PI * std::pow(smoothing_length, 3)); + else + throw std::runtime_error("Tdim is invalid"); + + // Compute basis function + double dw_dr = multiplier; + const double radius = norm_distance / smoothing_length; + if (radius >= 0.0 && radius < 1.0) + dw_dr *= + (-5.0 * std::pow(3.0 - radius, 4) + 30. * std::pow(2.0 - radius, 4) - + 75. * std::pow(1.0 - radius, 4)); + else if (radius >= 1.0 && radius < 2.0) + dw_dr *= + (-5.0 * std::pow(3.0 - radius, 4) + 30. * std::pow(2.0 - radius, 4)); + else if (radius >= 2.0 && radius < 3.0) + dw_dr *= (-5.0 * std::pow(3.0 - radius, 4)); + else + dw_dr = 0.0; + + return dw_dr; +} + +//! Gaussian Kernel +//! Source: Liu, 2010 +template +double gaussian(double smoothing_length, double norm_distance) { + + // Assign multiplier depends on dimension + double multiplier; + if (Tdim == 2 || Tdim == 3) + multiplier = + 1.0 / std::pow((std::sqrt(M_PI) * smoothing_length), int(Tdim)); + else + throw std::runtime_error("Tdim is invalid"); + + // Compute basis function + double basis_function = multiplier; + const double radius = norm_distance / smoothing_length; + if (radius >= 0.0 && radius <= 3.0) + basis_function *= std::exp(-std::pow(radius, 2)); + else + basis_function = 0.0; + + return basis_function; +} + +//! Gaussian Kernel derivative +//! Source: Liu, 2010 +template +double gaussian_derivative(double smoothing_length, double norm_distance) { + + // Assign multiplier depends on dimension + double multiplier; + if (Tdim == 2 || Tdim == 3) + multiplier = + 1.0 / std::pow((std::sqrt(M_PI) * smoothing_length), int(Tdim)); + else + throw std::runtime_error("Tdim is invalid"); + + // Compute basis function + double dw_dr = multiplier; + const double radius = norm_distance / smoothing_length; + if (radius >= 0.0 && radius < 3.0) + dw_dr *= -2.0 * radius * std::exp(-std::pow(radius, 2)); + else + dw_dr = 0.0; + + return dw_dr; +} + +//! Super Gaussian Kernel +//! Source: Monaghan, 1992 +template +double super_gaussian(double smoothing_length, double norm_distance) { + + // Assign multiplier depends on dimension + double multiplier; + if (Tdim == 2 || Tdim == 3) + multiplier = + 1.0 / std::pow((std::sqrt(M_PI) * smoothing_length), int(Tdim)); + else + throw std::runtime_error("Tdim is invalid"); + + // Compute basis function + double basis_function = multiplier; + const double radius = norm_distance / smoothing_length; + if (radius >= 0.0 && radius <= 3.0) + basis_function *= std::exp(-std::pow(radius, 2)) * + (double(Tdim) / 2. + 1. - radius * radius); + else + basis_function = 0.0; + + return basis_function; +} + +//! Super Gaussian Kernel derivative +//! Source: Monaghan, 1992 +template +double super_gaussian_derivative(double smoothing_length, + double norm_distance) { + + // Assign multiplier depends on dimension + double multiplier; + if (Tdim == 2 || Tdim == 3) + multiplier = + 1.0 / std::pow((std::sqrt(M_PI) * smoothing_length), int(Tdim)); + else + throw std::runtime_error("Tdim is invalid"); + + // Compute basis function + double dw_dr = multiplier; + const double radius = norm_distance / smoothing_length; + if (radius >= 0.0 && radius <= 3.0) + dw_dr *= std::exp(-std::pow(radius, 2)) * radius * + (-(double)Tdim + 2. * radius * radius - 4.); + else + dw_dr = 0.0; + + return dw_dr; +} + +} // namespace + +//! General Radial Basis Function Kernel call +template +double kernel(double smoothing_length, double norm_distance, + const std::string& type = "cubic_spline") { + // Norm distance should be positive + assert(norm_distance >= 0.0); + + if (type == "cubic_spline") { + return cubic_spline(smoothing_length, norm_distance); + } else if (type == "quintic_spline") { + return quintic_spline(smoothing_length, norm_distance); + } else if (type == "gaussian") { + return gaussian(smoothing_length, norm_distance); + } else if (type == "super_gaussian") { + return super_gaussian(smoothing_length, norm_distance); + } else { + throw std::runtime_error( + "RadialBasisFunction kernel type is invalid. Available types are: " + "\"cubic_spline\", \"quintic_spline\", \"gaussian\", and, " + "\"super_gaussian\"."); + } +} + +//! General Radial Basis Function Kernel call +template +Eigen::Matrix gradient( + double smoothing_length, + const Eigen::Matrix& relative_distance, + const std::string& type = "cubic_spline") { + + // Compute norm distance + const double norm_distance = relative_distance.norm(); + double dw_dr; + if (type == "cubic_spline") { + dw_dr = cubic_spline_derivative(smoothing_length, norm_distance); + } else if (type == "quintic_spline") { + dw_dr = quintic_spline_derivative(smoothing_length, norm_distance); + } else if (type == "gaussian") { + dw_dr = gaussian_derivative(smoothing_length, norm_distance); + } else if (type == "super_gaussian") { + dw_dr = super_gaussian_derivative(smoothing_length, norm_distance); + } else { + throw std::runtime_error( + "RadialBasisFunction gradient type is invalid. Available types are: " + "\"cubic_spline\", \"quintic_spline\", \"gaussian\", and, " + "\"super_gaussian\"."); + } + + // Gradient = dw_dr * r / ||r|| / h + Eigen::Matrix gradient = relative_distance; + if (norm_distance > std::numeric_limits::epsilon()) + gradient *= dw_dr / (norm_distance * smoothing_length); + else + gradient *= 0.0; + + return gradient; +} + +} // namespace RadialBasisFunction + +} // namespace mpm +#endif \ No newline at end of file diff --git a/src/io/logger.cc b/src/io/logger.cc index 3588d5afd..1bd5de90c 100644 --- a/src/io/logger.cc +++ b/src/io/logger.cc @@ -39,3 +39,18 @@ const std::shared_ptr mpm::Logger::mpm_explicit_usl_logger = // Create a logger for MPM Explicit MUSL const std::shared_ptr mpm::Logger::mpm_explicit_musl_logger = spdlog::stdout_color_st("MPMExplicitMUSL"); + +// Create a logger for MPM Semi Implicit Navier Stokes +const std::shared_ptr + mpm::Logger::mpm_semi_implicit_navier_stokes_logger = + spdlog::stdout_color_st("MPMSemiImplicitNavierStokes"); + +// Create a logger for MPM Explicit Two Phase +const std::shared_ptr + mpm::Logger::mpm_explicit_two_phase_logger = + spdlog::stdout_color_st("MPMExplicitTwoPhase"); + +// Create a logger for MPM Semi Implicit Two Phase +const std::shared_ptr + mpm::Logger::mpm_semi_implicit_two_phase_logger = + spdlog::stdout_color_st("MPMSemiImplicitTwoPhase"); diff --git a/src/io/partio_writer.cc b/src/io/partio_writer.cc index 9685ecada..854d9e23a 100644 --- a/src/io/partio_writer.cc +++ b/src/io/partio_writer.cc @@ -4,7 +4,7 @@ // Write particles bool mpm::partio::write_particles( const std::string& filename, - const std::vector& particles) { + const std::vector& particles) { bool status = false; if (!particles.empty()) { diff --git a/src/linear_solver.cc b/src/linear_solver.cc new file mode 100644 index 000000000..ca6a23145 --- /dev/null +++ b/src/linear_solver.cc @@ -0,0 +1,42 @@ +#include "assembler_base.h" +#include "assembler_eigen_semi_implicit_navierstokes.h" +#include "assembler_eigen_semi_implicit_twophase.h" + +#include "iterative_eigen.h" +#include "krylov_petsc.h" +#include "solver_base.h" + +// Assembler collections +// Asssembler 2D for NavierStokes +static Register, + mpm::AssemblerEigenSemiImplicitNavierStokes<2>, unsigned> + assembler_eigen_semi_implicit_navierstokes_2d( + "EigenSemiImplicitNavierStokes2D"); +// Asssembler 3D for NavierStokes +static Register, + mpm::AssemblerEigenSemiImplicitNavierStokes<3>, unsigned> + assembler_eigen_semi_implicit_navierstokes_3d( + "EigenSemiImplicitNavierStokes3D"); + +// Asssembler 2D for TwoPhase +static Register, + mpm::AssemblerEigenSemiImplicitTwoPhase<2>, unsigned> + assembler_eigen_semi_implicit_twophase_2d("EigenSemiImplicitTwoPhase2D"); +// Asssembler 3D for TwoPhase +static Register, + mpm::AssemblerEigenSemiImplicitTwoPhase<3>, unsigned> + assembler_eigen_semi_implicit_twophase_3d("EigenSemiImplicitTwoPhase3D"); + +// Linear Solver collections +// Eigen Conjugate Gradient +static Register>, + mpm::IterativeEigen>, unsigned, + double> + solver_iterative_eigen("IterativeEigen"); + +// Krylov Methods PTSC +#ifdef USE_PETSC +static Register>, + mpm::KrylovPETSC>, unsigned, double> + solver_krylov_petsc("KrylovPETSC"); +#endif diff --git a/src/main.cc b/src/main.cc index 7a9c1fc82..136c0901e 100644 --- a/src/main.cc +++ b/src/main.cc @@ -4,6 +4,9 @@ #ifdef USE_MPI #include "mpi.h" #endif +#ifdef USE_PETSC +#include +#endif #include "spdlog/spdlog.h" #include "git.h" @@ -29,6 +32,11 @@ int main(int argc, char** argv) { #endif +#ifdef USE_PETSC + // Initialize PETSc + PetscInitialize(&argc, &argv, 0, 0); +#endif + try { // Logger level (trace, debug, info, warn, error, critical, off) spdlog::set_level(spdlog::level::trace); @@ -60,6 +68,12 @@ int main(int argc, char** argv) { } catch (std::exception& exception) { std::cerr << "MPM main: " << exception.what() << std::endl; + +#ifdef USE_PETSC + // Finalize PETSc + PetscFinalize(); +#endif + #ifdef USE_MPI free(mpi_buffer); MPI_Buffer_detach(&mpi_buffer, &mpi_buffer_size); diff --git a/src/mpm.cc b/src/mpm.cc index d57b3a9a8..2b70f95ea 100644 --- a/src/mpm.cc +++ b/src/mpm.cc @@ -4,6 +4,9 @@ #include "io.h" #include "mpm.h" #include "mpm_explicit.h" +#include "mpm_explicit_twophase.h" +#include "mpm_semi_implicit_navierstokes.h" +#include "mpm_semi_implicit_twophase.h" namespace mpm { // 2D Explicit MPM @@ -14,4 +17,33 @@ static Register, const std::shared_ptr&> static Register, const std::shared_ptr&> mpm_explicit_3d("MPMExplicit3D"); +// 2D SemiImplicit Navier Stokes MPM +static Register, + const std::shared_ptr&> + mpm_semi_implicit_navierstokes_2d("MPMSemiImplicitNavierStokes2D"); + +// 3D SemiImplicit Navier Stokes MPM +static Register, + const std::shared_ptr&> + mpm_semi_implicit_navierstokes_3d("MPMSemiImplicitNavierStokes3D"); + +// 2D Explicit Two Phase MPM +static Register, + const std::shared_ptr&> + mpm_explicit_twophase_2d("MPMExplicitTwoPhase2D"); + +// 3D Explicit Two Phase MPM +static Register, + const std::shared_ptr&> + mpm_explicit_twophase_3d("MPMExplicitTwoPhase3D"); + +// 2D SemiImplicit Two Phase MPM +static Register, + const std::shared_ptr&> + mpm_semi_implicit_twophase_2d("MPMSemiImplicitTwoPhase2D"); + +// 3D SemiImplicit Two Phase MPM +static Register, + const std::shared_ptr&> + mpm_semi_implicit_twophase_3d("MPMSemiImplicitTwoPhase3D"); } // namespace mpm diff --git a/src/node.cc b/src/node.cc index 8c7ce2748..ea93ebb86 100644 --- a/src/node.cc +++ b/src/node.cc @@ -11,3 +11,13 @@ static Register, mpm::Node<2, 2, 1>, mpm::Index, static Register, mpm::Node<3, 3, 1>, mpm::Index, const Eigen::Matrix&> node3d("N3D"); + +// Node2D (2 DoF, 2 Phase) +static Register, mpm::Node<2, 2, 2>, mpm::Index, + const Eigen::Matrix&> + node2d2phase("N2D2P"); + +// Node3D (3 DoF, 2 Phase) +static Register, mpm::Node<3, 3, 2>, mpm::Index, + const Eigen::Matrix&> + node3d2phase("N3D2P"); \ No newline at end of file diff --git a/src/particle.cc b/src/particle.cc index b9f656edd..de411d0ac 100644 --- a/src/particle.cc +++ b/src/particle.cc @@ -1,11 +1,24 @@ #include "particle.h" #include "factory.h" #include "particle_base.h" +#include "particle_fluid.h" +#include "particle_twophase.h" namespace mpm { // ParticleType -std::map ParticleType = {{"P2D", 0}, {"P3D", 1}}; -std::map ParticleTypeName = {{0, "P2D"}, {1, "P3D"}}; +std::map ParticleType = {{"P2D", 0}, {"P3D", 1}, + {"P2DFLUID", 2}, {"P3DFLUID", 3}, + {"P2D2PHASE", 4}, {"P3D2PHASE", 5}}; +std::map ParticleTypeName = { + {0, "P2D"}, {1, "P3D"}, {2, "P2DFLUID"}, + {3, "P3DFLUID"}, {4, "P2D2PHASE"}, {5, "P3D2PHASE"}}; +std::map ParticlePODTypeName = { + {"P2D", "particles"}, + {"P3D", "particles"}, + {"P2DFLUID", "fluid_particles"}, + {"P3DFLUID", "fluid_particles"}, + {"P2D2PHASE", "twophase_particles"}, + {"P3D2PHASE", "twophase_particles"}}; } // namespace mpm // Particle2D (2 Dim) @@ -17,3 +30,23 @@ static Register, mpm::Particle<2>, mpm::Index, static Register, mpm::Particle<3>, mpm::Index, const Eigen::Matrix&> particle3d("P3D"); + +// Single phase (fluid) particle2D (2 Dim) +static Register, mpm::FluidParticle<2>, mpm::Index, + const Eigen::Matrix&> + particle2dfluid("P2DFLUID"); + +// Single phase (fluid) particle3D (3 Dim) +static Register, mpm::FluidParticle<3>, mpm::Index, + const Eigen::Matrix&> + particle3dfluid("P3DFLUID"); + +// Two-phase particle2D (2 Dim) +static Register, mpm::TwoPhaseParticle<2>, mpm::Index, + const Eigen::Matrix&> + particle2d2phase("P2D2PHASE"); + +// Two-phase particle3D (3 Dim) +static Register, mpm::TwoPhaseParticle<3>, mpm::Index, + const Eigen::Matrix&> + particle3d2phase("P3D2PHASE"); diff --git a/src/hdf5_particle.cc b/src/pod_particle.cc similarity index 65% rename from src/hdf5_particle.cc rename to src/pod_particle.cc index 0b42b19a5..603a8f2af 100644 --- a/src/hdf5_particle.cc +++ b/src/pod_particle.cc @@ -1,65 +1,65 @@ -#include "hdf5_particle.h" +#include "pod_particle.h" namespace mpm { -namespace hdf5 { +namespace pod { namespace particle { const size_t dst_offset[NFIELDS] = { - HOFFSET(HDF5Particle, id), - HOFFSET(HDF5Particle, mass), - HOFFSET(HDF5Particle, volume), - HOFFSET(HDF5Particle, pressure), - HOFFSET(HDF5Particle, coord_x), - HOFFSET(HDF5Particle, coord_y), - HOFFSET(HDF5Particle, coord_z), - HOFFSET(HDF5Particle, displacement_x), - HOFFSET(HDF5Particle, displacement_y), - HOFFSET(HDF5Particle, displacement_z), - HOFFSET(HDF5Particle, nsize_x), - HOFFSET(HDF5Particle, nsize_y), - HOFFSET(HDF5Particle, nsize_z), - HOFFSET(HDF5Particle, velocity_x), - HOFFSET(HDF5Particle, velocity_y), - HOFFSET(HDF5Particle, velocity_z), - HOFFSET(HDF5Particle, stress_xx), - HOFFSET(HDF5Particle, stress_yy), - HOFFSET(HDF5Particle, stress_zz), - HOFFSET(HDF5Particle, tau_xy), - HOFFSET(HDF5Particle, tau_yz), - HOFFSET(HDF5Particle, tau_xz), - HOFFSET(HDF5Particle, strain_xx), - HOFFSET(HDF5Particle, strain_yy), - HOFFSET(HDF5Particle, strain_zz), - HOFFSET(HDF5Particle, gamma_xy), - HOFFSET(HDF5Particle, gamma_yz), - HOFFSET(HDF5Particle, gamma_xz), - HOFFSET(HDF5Particle, epsilon_v), - HOFFSET(HDF5Particle, status), - HOFFSET(HDF5Particle, cell_id), - HOFFSET(HDF5Particle, material_id), - HOFFSET(HDF5Particle, nstate_vars), - HOFFSET(HDF5Particle, svars[0]), - HOFFSET(HDF5Particle, svars[1]), - HOFFSET(HDF5Particle, svars[2]), - HOFFSET(HDF5Particle, svars[3]), - HOFFSET(HDF5Particle, svars[4]), - HOFFSET(HDF5Particle, svars[5]), - HOFFSET(HDF5Particle, svars[6]), - HOFFSET(HDF5Particle, svars[7]), - HOFFSET(HDF5Particle, svars[8]), - HOFFSET(HDF5Particle, svars[9]), - HOFFSET(HDF5Particle, svars[10]), - HOFFSET(HDF5Particle, svars[11]), - HOFFSET(HDF5Particle, svars[12]), - HOFFSET(HDF5Particle, svars[13]), - HOFFSET(HDF5Particle, svars[14]), - HOFFSET(HDF5Particle, svars[15]), - HOFFSET(HDF5Particle, svars[16]), - HOFFSET(HDF5Particle, svars[17]), - HOFFSET(HDF5Particle, svars[18]), - HOFFSET(HDF5Particle, svars[19]), + HOFFSET(PODParticle, id), + HOFFSET(PODParticle, mass), + HOFFSET(PODParticle, volume), + HOFFSET(PODParticle, pressure), + HOFFSET(PODParticle, coord_x), + HOFFSET(PODParticle, coord_y), + HOFFSET(PODParticle, coord_z), + HOFFSET(PODParticle, displacement_x), + HOFFSET(PODParticle, displacement_y), + HOFFSET(PODParticle, displacement_z), + HOFFSET(PODParticle, nsize_x), + HOFFSET(PODParticle, nsize_y), + HOFFSET(PODParticle, nsize_z), + HOFFSET(PODParticle, velocity_x), + HOFFSET(PODParticle, velocity_y), + HOFFSET(PODParticle, velocity_z), + HOFFSET(PODParticle, stress_xx), + HOFFSET(PODParticle, stress_yy), + HOFFSET(PODParticle, stress_zz), + HOFFSET(PODParticle, tau_xy), + HOFFSET(PODParticle, tau_yz), + HOFFSET(PODParticle, tau_xz), + HOFFSET(PODParticle, strain_xx), + HOFFSET(PODParticle, strain_yy), + HOFFSET(PODParticle, strain_zz), + HOFFSET(PODParticle, gamma_xy), + HOFFSET(PODParticle, gamma_yz), + HOFFSET(PODParticle, gamma_xz), + HOFFSET(PODParticle, epsilon_v), + HOFFSET(PODParticle, status), + HOFFSET(PODParticle, cell_id), + HOFFSET(PODParticle, material_id), + HOFFSET(PODParticle, nstate_vars), + HOFFSET(PODParticle, svars[0]), + HOFFSET(PODParticle, svars[1]), + HOFFSET(PODParticle, svars[2]), + HOFFSET(PODParticle, svars[3]), + HOFFSET(PODParticle, svars[4]), + HOFFSET(PODParticle, svars[5]), + HOFFSET(PODParticle, svars[6]), + HOFFSET(PODParticle, svars[7]), + HOFFSET(PODParticle, svars[8]), + HOFFSET(PODParticle, svars[9]), + HOFFSET(PODParticle, svars[10]), + HOFFSET(PODParticle, svars[11]), + HOFFSET(PODParticle, svars[12]), + HOFFSET(PODParticle, svars[13]), + HOFFSET(PODParticle, svars[14]), + HOFFSET(PODParticle, svars[15]), + HOFFSET(PODParticle, svars[16]), + HOFFSET(PODParticle, svars[17]), + HOFFSET(PODParticle, svars[18]), + HOFFSET(PODParticle, svars[19]), }; // Get size of particle -HDF5Particle particle; +PODParticle particle; const size_t dst_sizes[NFIELDS] = { sizeof(particle.id), sizeof(particle.mass), @@ -190,5 +190,5 @@ const hid_t field_type[NFIELDS] = { H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE}; } // namespace particle -} // namespace hdf5 +} // namespace pod } // namespace mpm diff --git a/src/pod_particle_twophase.cc b/src/pod_particle_twophase.cc new file mode 100644 index 000000000..dff757246 --- /dev/null +++ b/src/pod_particle_twophase.cc @@ -0,0 +1,242 @@ +#include "pod_particle_twophase.h" +namespace mpm { +namespace pod { +namespace particletwophase { +const size_t dst_offset[NFIELDS] = { + // Solid phase + HOFFSET(PODParticleTwoPhase, id), + HOFFSET(PODParticleTwoPhase, mass), + HOFFSET(PODParticleTwoPhase, volume), + HOFFSET(PODParticleTwoPhase, pressure), + HOFFSET(PODParticleTwoPhase, coord_x), + HOFFSET(PODParticleTwoPhase, coord_y), + HOFFSET(PODParticleTwoPhase, coord_z), + HOFFSET(PODParticleTwoPhase, displacement_x), + HOFFSET(PODParticleTwoPhase, displacement_y), + HOFFSET(PODParticleTwoPhase, displacement_z), + HOFFSET(PODParticleTwoPhase, nsize_x), + HOFFSET(PODParticleTwoPhase, nsize_y), + HOFFSET(PODParticleTwoPhase, nsize_z), + HOFFSET(PODParticleTwoPhase, velocity_x), + HOFFSET(PODParticleTwoPhase, velocity_y), + HOFFSET(PODParticleTwoPhase, velocity_z), + HOFFSET(PODParticleTwoPhase, stress_xx), + HOFFSET(PODParticleTwoPhase, stress_yy), + HOFFSET(PODParticleTwoPhase, stress_zz), + HOFFSET(PODParticleTwoPhase, tau_xy), + HOFFSET(PODParticleTwoPhase, tau_yz), + HOFFSET(PODParticleTwoPhase, tau_xz), + HOFFSET(PODParticleTwoPhase, strain_xx), + HOFFSET(PODParticleTwoPhase, strain_yy), + HOFFSET(PODParticleTwoPhase, strain_zz), + HOFFSET(PODParticleTwoPhase, gamma_xy), + HOFFSET(PODParticleTwoPhase, gamma_yz), + HOFFSET(PODParticleTwoPhase, gamma_xz), + HOFFSET(PODParticleTwoPhase, epsilon_v), + HOFFSET(PODParticleTwoPhase, status), + HOFFSET(PODParticleTwoPhase, cell_id), + HOFFSET(PODParticleTwoPhase, material_id), + HOFFSET(PODParticleTwoPhase, nstate_vars), + HOFFSET(PODParticleTwoPhase, svars[0]), + HOFFSET(PODParticleTwoPhase, svars[1]), + HOFFSET(PODParticleTwoPhase, svars[2]), + HOFFSET(PODParticleTwoPhase, svars[3]), + HOFFSET(PODParticleTwoPhase, svars[4]), + HOFFSET(PODParticleTwoPhase, svars[5]), + HOFFSET(PODParticleTwoPhase, svars[6]), + HOFFSET(PODParticleTwoPhase, svars[7]), + HOFFSET(PODParticleTwoPhase, svars[8]), + HOFFSET(PODParticleTwoPhase, svars[9]), + HOFFSET(PODParticleTwoPhase, svars[10]), + HOFFSET(PODParticleTwoPhase, svars[11]), + HOFFSET(PODParticleTwoPhase, svars[12]), + HOFFSET(PODParticleTwoPhase, svars[13]), + HOFFSET(PODParticleTwoPhase, svars[14]), + HOFFSET(PODParticleTwoPhase, svars[15]), + HOFFSET(PODParticleTwoPhase, svars[16]), + HOFFSET(PODParticleTwoPhase, svars[17]), + HOFFSET(PODParticleTwoPhase, svars[18]), + HOFFSET(PODParticleTwoPhase, svars[19]), + // Fluid phase + HOFFSET(PODParticleTwoPhase, liquid_mass), + HOFFSET(PODParticleTwoPhase, liquid_velocity_x), + HOFFSET(PODParticleTwoPhase, liquid_velocity_y), + HOFFSET(PODParticleTwoPhase, liquid_velocity_z), + HOFFSET(PODParticleTwoPhase, porosity), + HOFFSET(PODParticleTwoPhase, liquid_saturation), + HOFFSET(PODParticleTwoPhase, liquid_material_id), + HOFFSET(PODParticleTwoPhase, nliquid_state_vars), + HOFFSET(PODParticleTwoPhase, liquid_svars[0]), + HOFFSET(PODParticleTwoPhase, liquid_svars[1]), + HOFFSET(PODParticleTwoPhase, liquid_svars[2]), + HOFFSET(PODParticleTwoPhase, liquid_svars[3]), + HOFFSET(PODParticleTwoPhase, liquid_svars[4]), +}; + +// Get size of particletwophase +PODParticleTwoPhase particle; +const size_t dst_sizes[NFIELDS] = { + // Solid phase + sizeof(particle.id), + sizeof(particle.mass), + sizeof(particle.volume), + sizeof(particle.pressure), + sizeof(particle.coord_x), + sizeof(particle.coord_y), + sizeof(particle.coord_z), + sizeof(particle.displacement_x), + sizeof(particle.displacement_y), + sizeof(particle.displacement_z), + sizeof(particle.nsize_x), + sizeof(particle.nsize_y), + sizeof(particle.nsize_z), + sizeof(particle.velocity_x), + sizeof(particle.velocity_y), + sizeof(particle.velocity_z), + sizeof(particle.stress_xx), + sizeof(particle.stress_yy), + sizeof(particle.stress_zz), + sizeof(particle.tau_xy), + sizeof(particle.tau_yz), + sizeof(particle.tau_xz), + sizeof(particle.strain_xx), + sizeof(particle.strain_yy), + sizeof(particle.strain_zz), + sizeof(particle.gamma_xy), + sizeof(particle.gamma_yz), + sizeof(particle.gamma_xz), + sizeof(particle.epsilon_v), + sizeof(particle.status), + sizeof(particle.cell_id), + sizeof(particle.material_id), + sizeof(particle.nstate_vars), + sizeof(particle.svars[0]), + sizeof(particle.svars[1]), + sizeof(particle.svars[2]), + sizeof(particle.svars[3]), + sizeof(particle.svars[4]), + sizeof(particle.svars[5]), + sizeof(particle.svars[6]), + sizeof(particle.svars[7]), + sizeof(particle.svars[8]), + sizeof(particle.svars[9]), + sizeof(particle.svars[10]), + sizeof(particle.svars[11]), + sizeof(particle.svars[12]), + sizeof(particle.svars[13]), + sizeof(particle.svars[14]), + sizeof(particle.svars[15]), + sizeof(particle.svars[16]), + sizeof(particle.svars[17]), + sizeof(particle.svars[18]), + sizeof(particle.svars[19]), + // Fluid phase + sizeof(particle.liquid_mass), + sizeof(particle.liquid_velocity_x), + sizeof(particle.liquid_velocity_y), + sizeof(particle.liquid_velocity_z), + sizeof(particle.porosity), + sizeof(particle.liquid_saturation), + sizeof(particle.liquid_material_id), + sizeof(particle.nliquid_state_vars), + sizeof(particle.liquid_svars[0]), + sizeof(particle.liquid_svars[1]), + sizeof(particle.liquid_svars[2]), + sizeof(particle.liquid_svars[3]), + sizeof(particle.liquid_svars[4]), +}; + +// Define particletwophase field information +const char* field_names[NFIELDS] = { + // Solid phase + "id", + "mass", + "volume", + "pressure", + "coord_x", + "coord_y", + "coord_z", + "displacement_x", + "displacement_y", + "displacement_z", + "nsize_x", + "nsize_y", + "nsize_z", + "velocity_x", + "velocity_y", + "velocity_z", + "stress_xx", + "stress_yy", + "stress_zz", + "tau_xy", + "tau_yz", + "tau_xz", + "strain_xx", + "strain_yy", + "strain_zz", + "gamma_xy", + "gamma_yz", + "gamma_xz", + "epsilon_v", + "status", + "cell_id", + "material_id", + "nstate_vars", + "svars_0", + "svars_1", + "svars_2", + "svars_3", + "svars_4", + "svars_5", + "svars_6", + "svars_7", + "svars_8", + "svars_9", + "svars_10", + "svars_11", + "svars_12", + "svars_13", + "svars_14", + "svars_15", + "svars_16", + "svars_17", + "svars_18", + "svars_19", + // Fluid phase + "liquid_mass", + "liquid_velocity_x", + "liquid_velocity_y", + "liquid_velocity_z", + "porosity", + "liquid_saturation", + "liquid_material_id", + "nliquid_state_vars", + "liquid_svars_0", + "liquid_svars_1", + "liquid_svars_2", + "liquid_svars_3", + "liquid_svars_4", +}; + +// Initialize field types +const hid_t field_type[NFIELDS] = { + H5T_NATIVE_LLONG, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_HBOOL, H5T_NATIVE_LLONG, H5T_NATIVE_UINT, + H5T_NATIVE_UINT, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_UINT, + H5T_NATIVE_UINT, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE, + H5T_NATIVE_DOUBLE, H5T_NATIVE_DOUBLE}; +} // namespace particletwophase +} // namespace pod +} // namespace mpm diff --git a/tests/functions/radial_basis_function_test.cc b/tests/functions/radial_basis_function_test.cc new file mode 100644 index 000000000..e522cad3c --- /dev/null +++ b/tests/functions/radial_basis_function_test.cc @@ -0,0 +1,539 @@ +#include +#include + +#include "Eigen/Dense" +#include "catch.hpp" + +#include "radial_basis_function.h" + +TEST_CASE("Radial basis function 2D", "[RBF][2D]") { + // Dimension + const unsigned Dim = 2; + // Tolerance + const double Tolerance = 1.E-7; + + SECTION("Kernel 2D") { + // Initialize variables + double smoothing_length, norm_distance; + std::string type; + + // Check error: wrong type name + type = "test_error"; + smoothing_length = 1.0; + norm_distance = 1.0; + REQUIRE_THROWS(mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type)); + + // Check error: wrong dimension + REQUIRE_THROWS(mpm::RadialBasisFunction::kernel<1>( + smoothing_length, norm_distance, "cubic_spline")); + REQUIRE_THROWS(mpm::RadialBasisFunction::kernel<1>( + smoothing_length, norm_distance, "quintic_spline")); + REQUIRE_THROWS(mpm::RadialBasisFunction::kernel<1>( + smoothing_length, norm_distance, "gaussian")); + REQUIRE_THROWS(mpm::RadialBasisFunction::kernel<1>( + smoothing_length, norm_distance, "super_gaussian")); + + // Cubic Spline + type = "cubic_spline"; + norm_distance = 3.0; + double kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 2.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 1.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.014210262776062).epsilon(Tolerance)); + + norm_distance = 1.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.113682102208497).epsilon(Tolerance)); + + norm_distance = 0.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.326836043849428).epsilon(Tolerance)); + + norm_distance = 0.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.454728408833987).epsilon(Tolerance)); + + // Quintic Spline + type = "quintic_spline"; + norm_distance = 5.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 3.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 2.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(2.08100082494633E-05).epsilon(Tolerance)); + + norm_distance = 2.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.000665920263983).epsilon(Tolerance)); + + norm_distance = 1.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.004931971955123).epsilon(Tolerance)); + + norm_distance = 1.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.017313926863554).epsilon(Tolerance)); + + norm_distance = 0.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.035002433875597).epsilon(Tolerance)); + + norm_distance = 0.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.043950737422867).epsilon(Tolerance)); + + // Gaussian + type = "gaussian"; + norm_distance = 3.0 + Tolerance; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 3.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(3.92825606927949E-05).epsilon(Tolerance)); + + norm_distance = 1.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.033549615174147).epsilon(Tolerance)); + + norm_distance = 0.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.318309886183791).epsilon(Tolerance)); + + // Super Gaussian + type = "super_gaussian"; + norm_distance = 3.0 + Tolerance; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 3.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(-0.00027497792485).epsilon(Tolerance)); + + norm_distance = 1.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(-0.008387403793537).epsilon(Tolerance)); + + norm_distance = 0.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.636619772367581).epsilon(Tolerance)); + } + + SECTION("Gradient 2D") { + // Initialize variables + double smoothing_length; + Eigen::Matrix relative_distance; + std::string type; + + // Check error: wrong type name + type = "test_error"; + smoothing_length = 1.0; + relative_distance.setZero(); + REQUIRE_THROWS(mpm::RadialBasisFunction::gradient( + smoothing_length, relative_distance, type)); + + // Check error: wrong dimension + Eigen::Matrix error_mat; + error_mat.setZero(); + REQUIRE_THROWS(mpm::RadialBasisFunction::gradient<1>( + smoothing_length, error_mat, "cubic_spline")); + REQUIRE_THROWS(mpm::RadialBasisFunction::gradient<1>( + smoothing_length, error_mat, "quintic_spline")); + REQUIRE_THROWS(mpm::RadialBasisFunction::gradient<1>( + smoothing_length, error_mat, "gaussian")); + REQUIRE_THROWS(mpm::RadialBasisFunction::gradient<1>( + smoothing_length, error_mat, "super_gaussian")); + + // Cubic Spline + type = "cubic_spline"; + relative_distance.setZero(); + Eigen::Matrix gradient = + mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + relative_distance << 0.5, -0.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.320358379080714).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.320358379080714).epsilon(Tolerance)); + + relative_distance << 1.0, -0.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.237280496186688).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.118640248093344).epsilon(Tolerance)); + + relative_distance << 1.5, 1.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + // Quintic Spline + type = "quintic_spline"; + relative_distance.setZero(); + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + relative_distance << 0.5, -0.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.025863567099382).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.025863567099382).epsilon(Tolerance)); + + relative_distance << 1.0, -0.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.026546314385505).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.013273157192753).epsilon(Tolerance)); + + relative_distance << -1.9, 1.3; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(0.000651627282552).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(-0.000445850245957).epsilon(Tolerance)); + + relative_distance << 2.5, 3.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + // Gaussian + type = "gaussian"; + relative_distance.setZero(); + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + relative_distance << -1.9, 1.3; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(0.00603772001586).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(-0.004131071589799).epsilon(Tolerance)); + + relative_distance << 2.5, 3.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + // Super Gaussian + type = "super_gaussian"; + relative_distance.setZero(); + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + relative_distance << -1.9, 1.3; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.013886756036479).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.009501464656538).epsilon(Tolerance)); + + relative_distance << 2.5, 3.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + } +} + +TEST_CASE("Radial basis function 3D", "[RBF][3D]") { + // Dimension + const unsigned Dim = 3; + // Tolerance + const double Tolerance = 1.E-9; + + SECTION("Kernel 3D") { + // Initialize variables + double smoothing_length, norm_distance; + std::string type; + + // Cubic Spline + type = "cubic_spline"; + smoothing_length = 1.0; + norm_distance = 3.0; + double kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 2.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 1.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.009947183943243).epsilon(Tolerance)); + + norm_distance = 1.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.079577471545948).epsilon(Tolerance)); + + norm_distance = 0.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.2287852306946).epsilon(Tolerance)); + + norm_distance = 0.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.318309886183791).epsilon(Tolerance)); + + // Quintic Spline + type = "quintic_spline"; + norm_distance = 5.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 3.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 2.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(8.31240998042629E-05).epsilon(Tolerance)); + + norm_distance = 2.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.002659971193736).epsilon(Tolerance)); + + norm_distance = 1.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.01970041165361).epsilon(Tolerance)); + + norm_distance = 1.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.069159251037147).epsilon(Tolerance)); + + norm_distance = 0.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.13981473587077).epsilon(Tolerance)); + + norm_distance = 0.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.175558098786603).epsilon(Tolerance)); + + // Gaussian + type = "gaussian"; + norm_distance = 3.0 + Tolerance; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 3.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(2.21628115579574E-05).epsilon(Tolerance)); + + norm_distance = 1.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.018928343413289).epsilon(Tolerance)); + + norm_distance = 0.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.179587122125167).epsilon(Tolerance)); + + // Super Gaussian + type = "super_gaussian"; + norm_distance = 3.0 + Tolerance; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.0).epsilon(Tolerance)); + + norm_distance = 3.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(-0.000144058275127).epsilon(Tolerance)); + + norm_distance = 1.5; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.004732085853322).epsilon(Tolerance)); + + norm_distance = 0.0; + kernel = mpm::RadialBasisFunction::kernel(smoothing_length, + norm_distance, type); + REQUIRE(kernel == Approx(0.448967805312916).epsilon(Tolerance)); + } + + SECTION("Gradient 3D") { + // Initialize variables + double smoothing_length = 1.0; + Eigen::Matrix relative_distance; + std::string type; + + // Cubic Spline + type = "cubic_spline"; + relative_distance.setZero(); + Eigen::Matrix gradient = + mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + relative_distance << 0.5, -0.4, 0.1; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.245390397939789).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.196312318351831).epsilon(Tolerance)); + REQUIRE(gradient(2) == Approx(-0.049078079587958).epsilon(Tolerance)); + + relative_distance << 1.0, -0.6, 0.3; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.1255681536468).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.07534089218808).epsilon(Tolerance)); + REQUIRE(gradient(2) == Approx(-0.03767044609404).epsilon(Tolerance)); + + relative_distance << 1.5, 1.5, 2.0; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + // Quintic Spline + type = "quintic_spline"; + relative_distance.setZero(); + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + relative_distance << 0.5, -0.5, 0.2; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.099803272175507).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.099803272175507).epsilon(Tolerance)); + REQUIRE(gradient(2) == Approx(-0.039921308870203).epsilon(Tolerance)); + + relative_distance << 0.5, -0.5, 1.3; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.022021780742007).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.022021780742007).epsilon(Tolerance)); + REQUIRE(gradient(2) == Approx(-0.057256629929218).epsilon(Tolerance)); + + relative_distance << -1.9, 1.3, 1.8; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(3.14726383394464E-07).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(-2.15339104427791E-07).epsilon(Tolerance)); + REQUIRE(gradient(2) == Approx(-2.98161836900018E-07).epsilon(Tolerance)); + + relative_distance << 2.5, 3.5, 3.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + // Gaussian + type = "gaussian"; + relative_distance.setZero(); + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + relative_distance << -1.9, 1.3, 1.0; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(0.001253151422955).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(-0.000857419394653).epsilon(Tolerance)); + REQUIRE(gradient(2) == Approx(-0.000659553380503).epsilon(Tolerance)); + + relative_distance << 2.5, 3.5, 3.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + // Super Gaussian + type = "super_gaussian"; + relative_distance.setZero(); + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + + relative_distance << -1.9, 1.3, 1.0; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + + REQUIRE(gradient(0) == Approx(-0.003508823984274).epsilon(Tolerance)); + REQUIRE(gradient(1) == Approx(0.00240077430503).epsilon(Tolerance)); + REQUIRE(gradient(2) == Approx(0.001846749465407).epsilon(Tolerance)); + + relative_distance << 2.5, 3.5, 3.5; + gradient = mpm::RadialBasisFunction::gradient(smoothing_length, + relative_distance, type); + for (unsigned i = 0; i < gradient.size(); ++i) + REQUIRE(gradient(i) == Approx(0.0).epsilon(Tolerance)); + } +} diff --git a/tests/graph_test.cc b/tests/graph_test.cc index 25baaf7bb..511bafc21 100644 --- a/tests/graph_test.cc +++ b/tests/graph_test.cc @@ -11,12 +11,12 @@ #include "data_types.h" #include "element.h" #include "graph.h" -#include "hdf5_particle.h" #include "hexahedron_element.h" #include "material.h" #include "mesh.h" #include "node.h" #include "particle.h" +#include "pod_particle.h" #include "quadrilateral_element.h" #include "vector.h" diff --git a/tests/include/write_mesh_particles.h b/tests/include/write_mesh_particles.h index 5e2f00980..1655d7f2c 100644 --- a/tests/include/write_mesh_particles.h +++ b/tests/include/write_mesh_particles.h @@ -8,6 +8,21 @@ namespace mpm_test { bool write_json(unsigned dim, bool resume, const std::string& analysis, const std::string& mpm_scheme, const std::string& file_name); +// Write JSON Configuration file for navierstokes +bool write_json_navierstokes(unsigned dim, bool resume, + const std::string& analysis, + const std::string& mpm_scheme, + const std::string& file_name, + const std::string& free_surface_type = "none", + const std::string& linear_solver_type = "none"); + +// Write JSON Configuration file for twophase +bool write_json_twophase(unsigned dim, bool resume, const std::string& analysis, + const std::string& mpm_scheme, + const std::string& file_name, + const std::string& free_surface_type = "none", + const std::string& linear_solver_type = "none"); + // Write JSON Entity Set bool write_entity_set(); diff --git a/tests/include/write_mesh_particles_unitcell.h b/tests/include/write_mesh_particles_unitcell.h index f0e084432..730ca8290 100644 --- a/tests/include/write_mesh_particles_unitcell.h +++ b/tests/include/write_mesh_particles_unitcell.h @@ -9,6 +9,18 @@ bool write_json_unitcell(unsigned dim, const std::string& analysis, const std::string& mpm_scheme, const std::string& file_name); +// Write JSON Configuration file for navier stokes +bool write_json_unitcell_navierstokes( + unsigned dim, const std::string& analysis, const std::string& mpm_scheme, + const std::string& file_name, const std::string& free_surface_type = "none", + const std::string& linear_solver_type = "none"); + +// Write JSON Configuration file for two-phase +bool write_json_unitcell_twophase( + unsigned dim, const std::string& analysis, const std::string& mpm_scheme, + const std::string& file_name, const std::string& free_surface_type = "none", + const std::string& linear_solver_type = "none"); + // Write Mesh file in 2D bool write_mesh_2d_unitcell(); // Write particles file in 2D diff --git a/tests/interface_test.cc b/tests/interface_test.cc index cb3b151d7..fa5eb7959 100644 --- a/tests/interface_test.cc +++ b/tests/interface_test.cc @@ -7,12 +7,12 @@ #include "cell.h" #include "element.h" #include "function_base.h" -#include "hdf5_particle.h" #include "hexahedron_element.h" #include "linear_function.h" #include "material.h" #include "node.h" #include "particle.h" +#include "pod_particle.h" #include "quadrilateral_element.h" //! \brief Check interface functions diff --git a/tests/io/io_mesh_ascii_test.cc b/tests/io/io_mesh_ascii_test.cc index 13779101b..5d5b22d7a 100644 --- a/tests/io/io_mesh_ascii_test.cc +++ b/tests/io/io_mesh_ascii_test.cc @@ -308,6 +308,59 @@ TEST_CASE("IOMeshAscii is checked for 2D", "[IOMesh][IOMeshAscii][2D]") { } } + // Check nodal pressure constraints file + SECTION("Check pressure constraints file") { + // Vector of friction constraints + std::vector> pressure_constraints; + + // Pressure constraint + pressure_constraints.emplace_back(std::make_tuple(0, 300.5)); + pressure_constraints.emplace_back(std::make_tuple(1, 500.5)); + pressure_constraints.emplace_back(std::make_tuple(2, 250.5)); + pressure_constraints.emplace_back(std::make_tuple(3, 0.0)); + + // Dump pressure constraints as an input file to be read + std::ofstream file; + file.open("pressure-constraints-2d.txt"); + // Write particle coordinates + for (const auto& pressure_constraint : pressure_constraints) { + file << std::get<0>(pressure_constraint) << "\t"; + file << std::get<1>(pressure_constraint) << "\t"; + + file << "\n"; + } + + file.close(); + + // Check read pressure constraints + SECTION("Check pressure constraints") { + // Create a read_mesh object + auto read_mesh = std::make_unique>(); + + // Try to read pressure constraints from a non-existant file + auto constraints = read_mesh->read_pressure_constraints( + "pressure-constraints-missing.txt"); + // Check number of constraints + REQUIRE(constraints.size() == 0); + + // Check constraints + constraints = + read_mesh->read_pressure_constraints("pressure-constraints-2d.txt"); + // Check number of particles + REQUIRE(constraints.size() == pressure_constraints.size()); + + // Check coordinates of nodes + for (unsigned i = 0; i < pressure_constraints.size(); ++i) { + REQUIRE( + std::get<0>(constraints.at(i)) == + Approx(std::get<0>(pressure_constraints.at(i))).epsilon(Tolerance)); + REQUIRE( + std::get<1>(constraints.at(i)) == + Approx(std::get<1>(pressure_constraints.at(i))).epsilon(Tolerance)); + } + } + } + SECTION("Check forces file") { // Vector of particle forces std::vector> nodal_forces; @@ -482,6 +535,51 @@ TEST_CASE("IOMeshAscii is checked for 2D", "[IOMesh][IOMeshAscii][2D]") { } } } + + SECTION("Check particles scalar properties file") { + // Particle scalar properties + std::map particles_scalars; + particles_scalars.emplace(std::make_pair(0, 10.5)); + particles_scalars.emplace(std::make_pair(1, -40.5)); + particles_scalars.emplace(std::make_pair(2, -60.5)); + particles_scalars.emplace(std::make_pair(3, 80.5)); + + // Dump particle scalar properties as an input file to be read + std::ofstream file; + file.open("particles-scalars-2d.txt"); + // Write particle scalar properties + for (const auto& scalars : particles_scalars) { + file << scalars.first << "\t"; + file << scalars.second << "\n"; + } + + file.close(); + + // Check read particles scalar properties file + SECTION("Check read particles scalar properties file") { + // Create a io_mesh object + auto io_mesh = std::make_unique>(); + + // Try to read particles scalar properties from a non-existant file + auto read_particles_scalars = io_mesh->read_particles_scalar_properties( + "particles-scalar-missing.txt"); + // Check number of particles scalar properties + REQUIRE(read_particles_scalars.size() == 0); + + // Check particles scalar properties + read_particles_scalars = + io_mesh->read_particles_scalar_properties("particles-scalars-2d.txt"); + + // Check number of particles + REQUIRE(read_particles_scalars.size() == particles_scalars.size()); + + // Check particles scalar properties + for (unsigned i = 0; i < particles_scalars.size(); ++i) { + REQUIRE(std::get<1>(read_particles_scalars.at(i)) == + Approx(particles_scalars.at(i)).epsilon(Tolerance)); + } + } + } } // Check IOMeshAscii @@ -828,6 +926,59 @@ TEST_CASE("IOMeshAscii is checked for 3D", "[IOMesh][IOMeshAscii][3D]") { } } + // Check nodal pressure constraints file + SECTION("Check pressure constraints file") { + // Vector of friction constraints + std::vector> pressure_constraints; + + // Pressure constraint + pressure_constraints.emplace_back(std::make_tuple(0, 300.5)); + pressure_constraints.emplace_back(std::make_tuple(1, 500.5)); + pressure_constraints.emplace_back(std::make_tuple(2, 250.5)); + pressure_constraints.emplace_back(std::make_tuple(3, 0.0)); + + // Dump pressure constraints as an input file to be read + std::ofstream file; + file.open("pressure-constraints-3d.txt"); + // Write particle coordinates + for (const auto& pressure_constraint : pressure_constraints) { + file << std::get<0>(pressure_constraint) << "\t"; + file << std::get<1>(pressure_constraint) << "\t"; + + file << "\n"; + } + + file.close(); + + // Check read pressure constraints + SECTION("Check pressure constraints") { + // Create a read_mesh object + auto read_mesh = std::make_unique>(); + + // Try to read pressure constraints from a non-existant file + auto constraints = read_mesh->read_pressure_constraints( + "pressure-constraints-missing.txt"); + // Check number of constraints + REQUIRE(constraints.size() == 0); + + // Check constraints + constraints = + read_mesh->read_pressure_constraints("pressure-constraints-3d.txt"); + // Check number of particles + REQUIRE(constraints.size() == pressure_constraints.size()); + + // Check coordinates of nodes + for (unsigned i = 0; i < pressure_constraints.size(); ++i) { + REQUIRE( + std::get<0>(constraints.at(i)) == + Approx(std::get<0>(pressure_constraints.at(i))).epsilon(Tolerance)); + REQUIRE( + std::get<1>(constraints.at(i)) == + Approx(std::get<1>(pressure_constraints.at(i))).epsilon(Tolerance)); + } + } + } + SECTION("Check forces file") { // Vector of particle forces std::vector> nodal_forces; @@ -1003,4 +1154,49 @@ TEST_CASE("IOMeshAscii is checked for 3D", "[IOMesh][IOMeshAscii][3D]") { } } } + + SECTION("Check particles scalar properties file") { + // Particle scalar properties + std::map particles_scalars; + particles_scalars.emplace(std::make_pair(0, 10.5)); + particles_scalars.emplace(std::make_pair(1, -40.5)); + particles_scalars.emplace(std::make_pair(2, -60.5)); + particles_scalars.emplace(std::make_pair(3, 80.5)); + + // Dump particle scalar properties as an input file to be read + std::ofstream file; + file.open("particles-scalars-3d.txt"); + // Write particle scalar properties + for (const auto& scalars : particles_scalars) { + file << scalars.first << "\t"; + file << scalars.second << "\n"; + } + + file.close(); + + // Check read particles scalar properties file + SECTION("Check read particles scalar properties file") { + // Create a io_mesh object + auto io_mesh = std::make_unique>(); + + // Try to read particles scalar properties from a non-existant file + auto read_particles_scalars = io_mesh->read_particles_scalar_properties( + "particles-scalar-missing.txt"); + // Check number of particles scalar properties + REQUIRE(read_particles_scalars.size() == 0); + + // Check particles scalar properties + read_particles_scalars = + io_mesh->read_particles_scalar_properties("particles-scalars-3d.txt"); + + // Check number of particles + REQUIRE(read_particles_scalars.size() == particles_scalars.size()); + + // Check particles scalar properties + for (unsigned i = 0; i < particles_scalars.size(); ++i) { + REQUIRE(std::get<1>(read_particles_scalars.at(i)) == + Approx(particles_scalars.at(i)).epsilon(Tolerance)); + } + } + } } diff --git a/tests/io/io_test.cc b/tests/io/io_test.cc index 9deb96f0e..97aa97776 100644 --- a/tests/io/io_test.cc +++ b/tests/io/io_test.cc @@ -41,7 +41,7 @@ TEST_CASE("IO is checked for input parsing", "[IO][JSON]") { {{"type", "MPMExplicitUSF3D"}, {"dt", 0.001}, {"nsteps", 1000}, - {"damping", {{"type", "Cundall"}, {"damping_ratio", 0.02}}}, + {"damping", {{"type", "Cundall"}, {"damping_factor", 0.02}}}, {"newmark", {{"newmark", true}, {"gamma", 0.5}, {"beta", 0.25}}}}}, {"post_processing", {{"path", "results/"}, {"output_steps", 10}}}}; diff --git a/tests/io/write_mesh_particles.cc b/tests/io/write_mesh_particles.cc index 9867615fb..2cb0c56f6 100644 --- a/tests/io/write_mesh_particles.cc +++ b/tests/io/write_mesh_particles.cc @@ -4,8 +4,7 @@ namespace mpm_test { // Write JSON Configuration file bool write_json(unsigned dim, bool resume, const std::string& analysis, - const std::string& stress_update, - const std::string& file_name) { + const std::string& mpm_scheme, const std::string& file_name) { // Make json object with input files // 2D std::string dimension = "2d"; @@ -86,7 +85,7 @@ bool write_json(unsigned dim, bool resume, const std::string& analysis, {"fxvalues", fxvalues}}}}, {"analysis", {{"type", analysis}, - {"stress_update", stress_update}, + {"mpm_scheme", mpm_scheme}, {"locate_particles", true}, {"dt", 0.001}, {"uuid", file_name + "-" + dimension}, @@ -96,7 +95,7 @@ bool write_json(unsigned dim, bool resume, const std::string& analysis, {{"resume", resume}, {"uuid", file_name + "-" + dimension}, {"step", 5}}}, - {"damping", {{"type", "Cundall"}, {"damping_ratio", 0.02}}}, + {"damping", {{"type", "Cundall"}, {"damping_factor", 0.02}}}, {"newmark", {{"newmark", true}, {"gamma", 0.5}, {"beta", 0.25}}}}}, {"post_processing", {{"path", "results/"}, @@ -114,18 +113,321 @@ bool write_json(unsigned dim, bool resume, const std::string& analysis, return true; } +// Write JSON Configuration file for navierstokes +bool write_json_navierstokes(unsigned dim, bool resume, + const std::string& analysis, + const std::string& mpm_scheme, + const std::string& file_name, + const std::string& free_surface_type, + const std::string& linear_solver_type) { + // Make json object with input files + // 2D + std::string dimension = "2d"; + auto particle_type = "P2DFLUID"; + auto node_type = "N2D"; + auto cell_type = "ED2Q4"; + auto io_type = "Ascii2D"; + auto assembler_type = "EigenSemiImplicitNavierStokes2D"; + std::string entity_set_name = "entity_sets_0"; + std::string material = "Newtonian2D"; + std::vector gravity{{0., -9.81}}; + std::vector material_id{{2}}; + std::vector xvalues{{0.0, 0.5, 1.0}}; + std::vector fxvalues{{0.0, 1.0, 1.0}}; + + // 3D + if (dim == 3) { + dimension = "3d"; + particle_type = "P3DFLUID"; + node_type = "N3D"; + cell_type = "ED3H8"; + assembler_type = "EigenSemiImplicitNavierStokes3D"; + io_type = "Ascii3D"; + material = "Newtonian3D"; + gravity.clear(); + gravity = {0., 0., -9.81}; + entity_set_name = "entity_sets_1"; + } + + Json json_file = { + {"title", "Example JSON Input for MPM"}, + {"mesh", + {{"mesh", "mesh-" + dimension + ".txt"}, + {"entity_sets", entity_set_name + ".json"}, + {"io_type", io_type}, + {"check_duplicates", true}, + {"isoparametric", false}, + {"node_type", node_type}, + {"boundary_conditions", + {{"velocity_constraints", {{"file", "velocity-constraints.txt"}}}, + {"pressure_constraints", + {{{"phase_id", 0}, {"nset_id", 1}, {"pressure", 0.0}}}}, + {"friction_constraints", {{"file", "friction-constraints.txt"}}}}}, + {"cell_type", cell_type}}}, + {"particles", + {{{"group_id", 0}, + {"generator", + {{"type", "file"}, + {"material_id", material_id}, + {"pset_id", 0}, + {"io_type", io_type}, + {"particle_type", particle_type}, + {"check_duplicates", true}, + {"location", "particles-" + dimension + ".txt"}}}}}}, + {"materials", + {{{"id", 2}, + {"type", material}, + {"density", 1000.}, + {"bulk_modulus", 1.E+9}, + {"mu", 0.3}, + {"dynamic_viscosity", 0.}}}}, + {"material_sets", + {{{"material_id", 1}, {"phase_id", 0}, {"pset_id", 2}}}}, + {"external_loading_conditions", + {{"gravity", gravity}, + {"particle_surface_traction", + {{{"math_function_id", 0}, + {"pset_id", -1}, + {"dir", 1}, + {"traction", 10.5}}}}, + {"concentrated_nodal_forces", + {{{"math_function_id", 0}, + {"nset_id", -1}, + {"dir", 1}, + {"force", 10.5}}}}}}, + {"math_functions", + {{{"id", 0}, + {"type", "Linear"}, + {"xvalues", xvalues}, + {"fxvalues", fxvalues}}}}, + {"analysis", + {{"type", analysis}, + {"mpm_scheme", mpm_scheme}, + {"locate_particles", true}, + {"pressure_smoothing", true}, + {"pore_pressure_smoothing", true}, + {"free_surface_detection", + {{"type", "density"}, {"volume_tolerance", 0.25}}}, + {"dt", 0.0001}, + {"uuid", file_name + "-" + dimension}, + {"nsteps", 10}, + {"boundary_friction", 0.5}, + {"resume", + {{"resume", resume}, + {"uuid", file_name + "-" + dimension}, + {"step", 5}}}, + {"semi_implicit", {{"beta", 1}, {"integration", "mp"}}}, + {"free_surface_detection", + {{"type", free_surface_type}, {"volume_tolerance", 0.25}}}, + {"linear_solver", + {{"assembler_type", assembler_type}, + {"solver_settings", + {{{"dof", "pressure"}, + {"solver_type", linear_solver_type}, + {"sub_solver_type", "cg"}, + {"preconditioner_type", "none"}, + {"max_iter", 100}, + {"tolerance", 1E-5}, + {"verbosity", 0}}}}}}, + {"damping", {{"type", "Cundall"}, {"damping_factor", 0.02}}}, + {"newmark", {{"newmark", true}, {"gamma", 0.5}, {"beta", 0.25}}}}}, + {"post_processing", + {{"path", "results/"}, + {"vtk", {"stresses", "strains", "velocity"}}, + {"vtk_statevars", {{{"phase_id", 0}, {"statevars", {"pdstrain"}}}}}, + {"output_steps", 5}}}}; + + // Dump JSON as an input file to be read + std::ofstream file; + file.open((file_name + "-" + dimension + ".json").c_str()); + file << json_file.dump(2); + file.close(); + + return true; +} + +// Write JSON Configuration file for twophase +bool write_json_twophase(unsigned dim, bool resume, const std::string& analysis, + const std::string& mpm_scheme, + const std::string& file_name, + const std::string& free_surface_type, + const std::string& linear_solver_type) { + // Make json object with input files + // 2D + std::string dimension = "2d"; + auto particle_type = "P2D2PHASE"; + auto node_type = "N2D2P"; + auto cell_type = "ED2Q4"; + auto io_type = "Ascii2D"; + auto assembler_type = "EigenSemiImplicitTwoPhase2D"; + std::string entity_set_name = "entity_sets_0"; + std::string material = "LinearElastic2D"; + std::string liquid_material = "Newtonian2D"; + std::vector gravity{{0., -9.81}}; + std::vector material_id{{0, 2}}; + std::vector xvalues{{0.0, 0.5, 1.0}}; + std::vector fxvalues{{0.0, 1.0, 1.0}}; + + // 3D + if (dim == 3) { + dimension = "3d"; + particle_type = "P3D2PHASE"; + node_type = "N3D2P"; + cell_type = "ED3H8"; + assembler_type = "EigenSemiImplicitTwoPhase3D"; + io_type = "Ascii3D"; + material = "LinearElastic3D"; + liquid_material = "Newtonian3D"; + gravity.clear(); + gravity = {0., 0., -9.81}; + entity_set_name = "entity_sets_1"; + } + + Json json_file = { + {"title", "Example JSON Input for MPM"}, + {"mesh", + {{"mesh", "mesh-" + dimension + ".txt"}, + {"entity_sets", entity_set_name + ".json"}, + {"io_type", io_type}, + {"check_duplicates", true}, + {"isoparametric", false}, + {"node_type", node_type}, + {"boundary_conditions", + {{"velocity_constraints", {{"file", "velocity-constraints.txt"}}}, + {"pressure_constraints", + {{{"phase_id", 1}, {"nset_id", 1}, {"pressure", 0.0}}}}, + {"friction_constraints", {{"file", "friction-constraints.txt"}}}}}, + {"cell_type", cell_type}}}, + {"particles", + {{{"group_id", 0}, + {"generator", + {{"type", "file"}, + {"material_id", material_id}, + {"pset_id", 0}, + {"io_type", io_type}, + {"particle_type", particle_type}, + {"check_duplicates", true}, + {"location", "particles-" + dimension + ".txt"}}}}}}, + {"materials", + {{{"id", 0}, + {"type", material}, + {"density", 1000.}, + {"youngs_modulus", 1.0E+8}, + {"poisson_ratio", 0.495}, + {"porosity", 0.3}, + {"k_x", 0.001}, + {"k_y", 0.001}, + {"k_z", 0.001}}, + {{"id", 1}, + {"type", material}, + {"density", 2300.}, + {"youngs_modulus", 1.5E+6}, + {"poisson_ratio", 0.25}, + {"porosity", 0.3}, + {"k_x", 0.001}, + {"k_y", 0.001}, + {"k_z", 0.001}}, + {{"id", 2}, + {"type", liquid_material}, + {"density", 1000.}, + {"bulk_modulus", 1.E+9}, + {"mu", 0.3}, + {"dynamic_viscosity", 0.}}}}, + {"material_sets", + {{{"material_id", 1}, {"phase_id", 0}, {"pset_id", 2}}}}, + {"external_loading_conditions", + {{"gravity", gravity}, + {"particle_surface_traction", + {{{"math_function_id", 0}, + {"pset_id", -1}, + {"dir", 1}, + {"traction", 10.5}}}}, + {"concentrated_nodal_forces", + {{{"math_function_id", 0}, + {"nset_id", -1}, + {"dir", 1}, + {"force", 10.5}}}}}}, + {"math_functions", + {{{"id", 0}, + {"type", "Linear"}, + {"xvalues", xvalues}, + {"fxvalues", fxvalues}}}}, + {"analysis", + {{"type", analysis}, + {"mpm_scheme", mpm_scheme}, + {"locate_particles", true}, + {"pressure_smoothing", true}, + {"pore_pressure_smoothing", true}, + {"free_surface_detection", + {{"type", "density"}, {"volume_tolerance", 0.25}}}, + {"dt", 0.0001}, + {"uuid", file_name + "-" + dimension}, + {"nsteps", 10}, + {"boundary_friction", 0.5}, + {"resume", + {{"resume", resume}, + {"uuid", file_name + "-" + dimension}, + {"step", 5}}}, + {"semi_implicit", {{"beta", 1}, {"integration", "mp"}}}, + {"free_surface_detection", + {{"type", free_surface_type}, {"volume_tolerance", 0.25}}}, + {"linear_solver", + {{"assembler_type", assembler_type}, + {"solver_settings", + {{{"dof", "pressure"}, + {"solver_type", linear_solver_type}, + {"sub_solver_type", "cg"}, + {"preconditioner_type", "none"}, + {"max_iter", 100}, + {"tolerance", 1E-5}, + {"verbosity", 0}}, + {{"dof", "acceleration"}, + {"solver_type", linear_solver_type}, + {"sub_solver_type", "lscg"}, + {"preconditioner_type", "none"}, + {"max_iter", 100}, + {"tolerance", 1E-5}, + {"verbosity", 0}}}}}}, + {"damping", {{"type", "Cundall"}, {"damping_factor", 0.02}}}, + {"newmark", {{"newmark", true}, {"gamma", 0.5}, {"beta", 0.25}}}}}, + {"post_processing", + {{"path", "results/"}, + {"vtk", {"stresses", "strains", "velocity"}}, + {"vtk_statevars", {{{"phase_id", 0}, {"statevars", {"pdstrain"}}}}}, + {"output_steps", 5}}}}; + + // Dump JSON as an input file to be read + std::ofstream file; + file.open((file_name + "-" + dimension + ".json").c_str()); + file << json_file.dump(2); + file.close(); + + return true; +} + // Write JSON Entity Set bool write_entity_set() { // JSON Entity Sets - Json json_file = { + Json json_file0 = { {"particle_sets", {{{"id", 2}, - {"set", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}}}}}; + {"set", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}}}}, + {"node_sets", {{{"id", 1}, {"set", {4, 5}}}}}}; + + Json json_file1 = { + {"particle_sets", + {{{"id", 2}, + {"set", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}}}}, + {"node_sets", {{{"id", 1}, {"set", {8, 9, 10, 11}}}}}}; // Dump JSON as an input file to be read std::ofstream file; file.open("entity_sets_0.json"); - file << json_file.dump(2); + file << json_file0.dump(2); + file.close(); + + file.open("entity_sets_1.json"); + file << json_file1.dump(2); file.close(); return true; @@ -193,7 +495,8 @@ bool write_mesh_2d() { // Dump mesh velocity constraints std::ofstream file_constraints; file_constraints.open("velocity-constraints.txt"); - file_constraints << 0 << "\t" << 0 << "\t" << 0 << "\n"; + file_constraints << 0 << "\t" << 1 << "\t" << 0 << "\n"; + file_constraints << 1 << "\t" << 1 << "\t" << 0 << "\n"; file_constraints.close(); return true; @@ -335,7 +638,10 @@ bool write_mesh_3d() { // Dump mesh velocity constraints std::ofstream file_constraints; file_constraints.open("velocity-constraints.txt"); - file_constraints << 0 << "\t" << 0 << "\t" << 0 << "\n"; + file_constraints << 0 << "\t" << 3 << "\t" << 0 << "\n"; + file_constraints << 1 << "\t" << 3 << "\t" << 0 << "\n"; + file_constraints << 2 << "\t" << 3 << "\t" << 0 << "\n"; + file_constraints << 3 << "\t" << 3 << "\t" << 0 << "\n"; file_constraints.close(); return true; diff --git a/tests/io/write_mesh_particles_unitcell.cc b/tests/io/write_mesh_particles_unitcell.cc index f6a17caec..56e320b0c 100644 --- a/tests/io/write_mesh_particles_unitcell.cc +++ b/tests/io/write_mesh_particles_unitcell.cc @@ -4,7 +4,7 @@ namespace mpm_test { // Write JSON Configuration file bool write_json_unitcell(unsigned dim, const std::string& analysis, - const std::string& stress_update, + const std::string& mpm_scheme, const std::string& file_name) { // Make json object with input files // 2D @@ -39,7 +39,8 @@ bool write_json_unitcell(unsigned dim, const std::string& analysis, {"isoparametric", false}, {"node_type", node_type}, {"boundary_conditions", - {{"velocity_constraints", {{"file", "velocity-constraints.txt"}}}, + {{"velocity_constraints", + {{"file", "velocity-constraints-unitcell.txt"}}}, {"friction_constraints", {{"file", "friction-constraints.txt"}}}}}, {"cell_type", cell_type}}}, {"particles", @@ -87,12 +88,12 @@ bool write_json_unitcell(unsigned dim, const std::string& analysis, {"fxvalues", fxvalues}}}}, {"analysis", {{"type", analysis}, - {"stress_update", stress_update}, + {"mpm_scheme", mpm_scheme}, {"locate_particles", true}, {"dt", 0.001}, {"nsteps", 10}, {"boundary_friction", 0.5}, - {"damping", {{"type", "Cundall"}, {"damping_ratio", 0.02}}}, + {"damping", {{"type", "Cundall"}, {"damping_factor", 0.02}}}, {"newmark", {{"newmark", true}, {"gamma", 0.5}, {"beta", 0.25}}}}}, {"post_processing", {{"path", "results/"}, @@ -111,6 +112,262 @@ bool write_json_unitcell(unsigned dim, const std::string& analysis, return true; } +// Write JSON Configuration file for two-phase +bool write_json_unitcell_navierstokes(unsigned dim, const std::string& analysis, + const std::string& mpm_scheme, + const std::string& file_name, + const std::string& free_surface_type, + const std::string& linear_solver_type) { + // Make json object with input files + // 2D + std::string dimension = "2d"; + auto particle_type = "P2DFLUID"; + auto node_type = "N2D"; + auto cell_type = "ED2Q4"; + auto io_type = "Ascii2D"; + auto assembler_type = "EigenSemiImplicitNavierStokes2D"; + std::string material = "Newtonian2D"; + std::vector gravity{{0., -9.81}}; + std::vector material_id{{2}}; + std::vector xvalues{{0.0, 0.5, 1.0}}; + std::vector fxvalues{{0.0, 1.0, 1.0}}; + + // 3D + if (dim == 3) { + dimension = "3d"; + particle_type = "P3DFLUID"; + node_type = "N3D"; + cell_type = "ED3H8"; + assembler_type = "EigenSemiImplicitNavierStokes3D"; + io_type = "Ascii3D"; + material = "Newtonian3D"; + gravity.clear(); + gravity = {0., 0., -9.81}; + } + + Json json_file = { + {"title", "Example JSON Input for MPM"}, + {"mesh", + {{"mesh", "mesh-" + dimension + "-unitcell.txt"}, + {"io_type", io_type}, + {"isoparametric", false}, + {"node_type", node_type}, + {"boundary_conditions", + {{"velocity_constraints", + {{"file", "velocity-constraints-unitcell.txt"}}}, + {"pressure_constraints", + {{{"phase_id", 1}, + {"file", "pore-pressure-constraints-unitcell.txt"}}}}, + {"friction_constraints", {{"file", "friction-constraints.txt"}}}}}, + {"cell_type", cell_type}}}, + {"particles", + {{{"group_id", 0}, + {"generator", + {{"type", "file"}, + {"io_type", io_type}, + {"material_id", material_id}, + {"pset_id", 0}, + {"particle_type", particle_type}, + {"check_duplicates", true}, + {"location", "particles-" + dimension + "-unitcell.txt"}}}}}}, + {"materials", + {{{"id", 2}, + {"type", material}, + {"density", 1000.}, + {"bulk_modulus", 1.E+9}, + {"mu", 0.3}, + {"dynamic_viscosity", 0.}}}}, + {"external_loading_conditions", + {{"gravity", gravity}, + {"particle_surface_traction", + {{{"math_function_id", 0}, + {"pset_id", -1}, + {"dir", 1}, + {"traction", 10.5}}}}, + {"concentrated_nodal_forces", + {{{"math_function_id", 0}, + {"nset_id", -1}, + {"dir", 1}, + {"force", 10.5}}}}}}, + {"math_functions", + {{{"id", 0}, + {"type", "Linear"}, + {"xvalues", xvalues}, + {"fxvalues", fxvalues}}}}, + {"math_functions", + {{{"id", 0}, + {"type", "Linear"}, + {"xvalues", xvalues}, + {"fxvalues", fxvalues}}}}, + {"analysis", + {{"type", analysis}, + {"mpm_scheme", mpm_scheme}, + {"locate_particles", true}, + {"pressure_smoothing", false}, + {"pore_pressure_smoothing", false}, + {"free_surface_detection", + {{"type", "geometry"}, {"volume_tolerance", 0.25}}}, + {"dt", 0.0001}, + {"nsteps", 10}, + {"boundary_friction", 0.5}, + {"semi_implicit", {{"beta", 1}, {"integration", "mp"}}}, + {"free_surface_detection", + {{"type", free_surface_type}, {"volume_tolerance", 0.25}}}, + {"linear_solver", {{"assembler_type", assembler_type}}}, + {"damping", {{"type", "Cundall"}, {"damping_factor", 0.02}}}, + {"newmark", {{"newmark", true}, {"gamma", 0.5}, {"beta", 0.25}}}}}, + {"post_processing", + {{"path", "results/"}, + {"vtk_statevars", {{{"phase_id", 0}, {"statevars", {"pdstrain"}}}}}, + {"output_steps", 10}}}}; + + // Dump JSON as an input file to be read + std::ofstream file; + file.open((file_name + "-" + dimension + "-unitcell.json").c_str()); + file << json_file.dump(2); + file.close(); + + return true; +} + +// Write JSON Configuration file for two-phase +bool write_json_unitcell_twophase(unsigned dim, const std::string& analysis, + const std::string& mpm_scheme, + const std::string& file_name, + const std::string& free_surface_type, + const std::string& linear_solver_type) { + // Make json object with input files + // 2D + std::string dimension = "2d"; + auto particle_type = "P2D2PHASE"; + auto node_type = "N2D2P"; + auto cell_type = "ED2Q4"; + auto io_type = "Ascii2D"; + auto assembler_type = "EigenSemiImplicitTwoPhase2D"; + std::string material = "LinearElastic2D"; + std::string liquid_material = "Newtonian2D"; + std::vector gravity{{0., -9.81}}; + std::vector material_id{{1, 2}}; + std::vector xvalues{{0.0, 0.5, 1.0}}; + std::vector fxvalues{{0.0, 1.0, 1.0}}; + + // 3D + if (dim == 3) { + dimension = "3d"; + particle_type = "P3D2PHASE"; + node_type = "N3D2P"; + cell_type = "ED3H8"; + assembler_type = "EigenSemiImplicitTwoPhase3D"; + io_type = "Ascii3D"; + material = "LinearElastic3D"; + liquid_material = "Newtonian3D"; + gravity.clear(); + gravity = {0., 0., -9.81}; + } + + Json json_file = { + {"title", "Example JSON Input for MPM"}, + {"mesh", + {{"mesh", "mesh-" + dimension + "-unitcell.txt"}, + {"io_type", io_type}, + {"isoparametric", false}, + {"node_type", node_type}, + {"boundary_conditions", + {{"velocity_constraints", + {{"file", "velocity-constraints-unitcell.txt"}}}, + {"pressure_constraints", + {{{"phase_id", 1}, + {"file", "pore-pressure-constraints-unitcell.txt"}}}}, + {"friction_constraints", {{"file", "friction-constraints.txt"}}}}}, + {"cell_type", cell_type}}}, + {"particles", + {{{"group_id", 0}, + {"generator", + {{"type", "file"}, + {"io_type", io_type}, + {"material_id", material_id}, + {"pset_id", 0}, + {"particle_type", particle_type}, + {"check_duplicates", true}, + {"location", "particles-" + dimension + "-unitcell.txt"}}}}}}, + {"materials", + {{{"id", 0}, + {"type", material}, + {"density", 1000.}, + {"youngs_modulus", 1.0E+8}, + {"poisson_ratio", 0.495}, + {"porosity", 0.3}, + {"k_x", 0.001}, + {"k_y", 0.001}, + {"k_z", 0.001}}, + {{"id", 1}, + {"type", material}, + {"density", 2300.}, + {"youngs_modulus", 1.5E+6}, + {"poisson_ratio", 0.25}, + {"porosity", 0.3}, + {"k_x", 0.001}, + {"k_y", 0.001}, + {"k_z", 0.001}}, + {{"id", 2}, + {"type", liquid_material}, + {"density", 2300.}, + {"bulk_modulus", 1.E+9}, + {"mu", 0.3}, + {"dynamic_viscosity", 0.}}}}, + {"external_loading_conditions", + {{"gravity", gravity}, + {"particle_surface_traction", + {{{"math_function_id", 0}, + {"pset_id", -1}, + {"dir", 1}, + {"traction", 10.5}}}}, + {"concentrated_nodal_forces", + {{{"math_function_id", 0}, + {"nset_id", -1}, + {"dir", 1}, + {"force", 10.5}}}}}}, + {"math_functions", + {{{"id", 0}, + {"type", "Linear"}, + {"xvalues", xvalues}, + {"fxvalues", fxvalues}}}}, + {"math_functions", + {{{"id", 0}, + {"type", "Linear"}, + {"xvalues", xvalues}, + {"fxvalues", fxvalues}}}}, + {"analysis", + {{"type", analysis}, + {"mpm_scheme", mpm_scheme}, + {"locate_particles", true}, + {"pressure_smoothing", false}, + {"pore_pressure_smoothing", false}, + {"free_surface_detection", + {{"type", "geometry"}, {"volume_tolerance", 0.25}}}, + {"dt", 0.0001}, + {"nsteps", 10}, + {"boundary_friction", 0.5}, + {"semi_implicit", {{"beta", 1}, {"integration", "mp"}}}, + {"free_surface_detection", + {{"type", free_surface_type}, {"volume_tolerance", 0.25}}}, + {"linear_solver", {{"assembler_type", assembler_type}}}, + {"damping", {{"type", "Cundall"}, {"damping_factor", 0.02}}}, + {"newmark", {{"newmark", true}, {"gamma", 0.5}, {"beta", 0.25}}}}}, + {"post_processing", + {{"path", "results/"}, + {"vtk_statevars", {{{"phase_id", 0}, {"statevars", {"pdstrain"}}}}}, + {"output_steps", 10}}}}; + + // Dump JSON as an input file to be read + std::ofstream file; + file.open((file_name + "-" + dimension + "-unitcell.json").c_str()); + file << json_file.dump(2); + file.close(); + + return true; +} + // Write Mesh file in 2D bool write_mesh_2d_unitcell() { // Dimension @@ -175,6 +432,11 @@ bool write_mesh_2d_unitcell() { std::ofstream file_constraints; file_constraints.open("velocity-constraints-unitcell.txt"); file_constraints << 0 << "\t" << 0 << "\t" << 0 << "\n"; + + // Dump mesh pressure constraints + file_constraints.open("pore-pressure-constraints-unitcell.txt"); + file_constraints << 4 << "\t" << 0 << "\n"; + file_constraints << 5 << "\t" << 0 << "\n"; file_constraints.close(); return true; @@ -321,7 +583,18 @@ bool write_mesh_3d_unitcell() { // Dump mesh velocity constraints std::ofstream file_constraints; file_constraints.open("velocity-constraints-unitcell.txt"); - file_constraints << 0 << "\t" << 0 << "\t" << 0 << "\n"; + file_constraints << 0 << "\t" << 3 << "\t" << 0 << "\n"; + file_constraints << 1 << "\t" << 3 << "\t" << 0 << "\n"; + file_constraints << 2 << "\t" << 3 << "\t" << 0 << "\n"; + file_constraints << 3 << "\t" << 3 << "\t" << 0 << "\n"; + file_constraints.close(); + + // Dump mesh pressure constraints + file_constraints.open("pore-pressure-constraints-unitcell.txt"); + file_constraints << 8 << "\t" << 0 << "\n"; + file_constraints << 9 << "\t" << 0 << "\n"; + file_constraints << 10 << "\t" << 0 << "\n"; + file_constraints << 11 << "\t" << 0 << "\n"; file_constraints.close(); return true; diff --git a/tests/linear_solver_test.cc b/tests/linear_solver_test.cc new file mode 100644 index 000000000..26d686fbb --- /dev/null +++ b/tests/linear_solver_test.cc @@ -0,0 +1,489 @@ +#include +#include + +#include +#include +#include +#include + +#include "catch.hpp" +#include "factory.h" +#include "solver_base.h" + +// Generate 3x3 test matrix +Eigen::SparseMatrix CreateSymmetricTestMatrix3x3() { + Eigen::SparseMatrix matrix(3, 3); + matrix.setZero(); + matrix.coeffRef(0, 0) = 4.0; + matrix.coeffRef(1, 1) = 4.0; + matrix.coeffRef(2, 2) = 4.0; + matrix.coeffRef(0, 1) = 1.0; + matrix.coeffRef(0, 2) = 1.0; + matrix.coeffRef(1, 2) = 1.0; + matrix.coeffRef(1, 0) = 1.0; + matrix.coeffRef(2, 0) = 1.0; + matrix.coeffRef(2, 1) = 1.0; + return matrix; +} + +// Generate random SPD coefficient matrix with specified dimension and seed +Eigen::SparseMatrix CreateRandomSymmetricTestMatrix(unsigned dim, + unsigned seed = 0) { + std::srand(seed); + Eigen::MatrixXd m = Eigen::MatrixXd::Random(dim, dim); + m = (m + Eigen::MatrixXd::Constant(dim, dim, 1.2)) * 50; + Eigen::SparseMatrix A = m.sparseView(); + Eigen::SparseMatrix AT = A.transpose(); + Eigen::SparseMatrix matrix = A + AT; + return matrix; +} + +// Generate random RHS vector with specified dimension and seed +Eigen::VectorXd CreateRandomRHSVector(unsigned dim, unsigned seed = 0) { + std::srand(seed); + Eigen::VectorXd vector = Eigen::VectorXd::Random(dim); + return vector; +} + +TEST_CASE("Linear solver test", "[linear_solver]") { + // Maximum iteration possible + unsigned max_iter = 100; + // Allowed relative tolerance + double rel_tolerance = 1.E-5; + // Tolerance + const double Tolerance = 1.E-6; + + SECTION("Eigen solver") { + // Construct Eigen solver + std::shared_ptr>> + eigen_matrix_solver = + Factory>, unsigned, + double>::instance() + ->create("IterativeEigen", std::move(max_iter), + std::move(rel_tolerance)); + + // Initiate sub_solver_type + std::string sub_solver_type = "cg"; + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type(sub_solver_type)); + + SECTION("Eigen CG 3x3 solver") { + // Construct matrix and vector + const auto& A = CreateSymmetricTestMatrix3x3(); + Eigen::VectorXd b(3); + b << 6.0, 3.0, -9.0; + + // Solve + auto x_eigen = eigen_matrix_solver->solve(A, b); + + // Check + REQUIRE(x_eigen(0) == Approx(2.).epsilon(Tolerance)); + REQUIRE(x_eigen(1) == Approx(1.).epsilon(Tolerance)); + REQUIRE(x_eigen(2) == Approx(-3.).epsilon(Tolerance)); + + SECTION("Eigen CG verbosity") { + // Verbosity 1 + REQUIRE_NOTHROW(eigen_matrix_solver->set_verbosity(1)); + + // Solve lscg + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("lscg")); + x_eigen.setZero(); + x_eigen = eigen_matrix_solver->solve(A, b); + + // Solve bicgstab + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("bicgstab")); + x_eigen = eigen_matrix_solver->solve(A, b); + + // Solve cg + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("cg")); + x_eigen = eigen_matrix_solver->solve(A, b); + + // Verbosity 3 + REQUIRE_NOTHROW(eigen_matrix_solver->set_verbosity(3)); + x_eigen = eigen_matrix_solver->solve(A, b); + } + } + + SECTION("Eigen CG 5x5 solver") { + // Construct matrix and vector + const auto& A = CreateRandomSymmetricTestMatrix(5); + const auto& b = CreateRandomRHSVector(5); + + // Solve + const auto& x_eigen = eigen_matrix_solver->solve(A, b); + + // Check + REQUIRE(x_eigen(0) == Approx(0.0120451).epsilon(Tolerance)); + REQUIRE(x_eigen(1) == Approx(0.0059998).epsilon(Tolerance)); + REQUIRE(x_eigen(2) == Approx(-0.0002562).epsilon(Tolerance)); + REQUIRE(x_eigen(3) == Approx(-0.00243321).epsilon(Tolerance)); + REQUIRE(x_eigen(4) == Approx(-0.0137986).epsilon(Tolerance)); + } + + SECTION("Eigen CG 50x50 solver") { + unsigned dim = 50; + + // Construct matrix and vector + const auto& A = CreateRandomSymmetricTestMatrix(dim, 1); + const auto& b = CreateRandomRHSVector(dim, 1); + + // Solve + auto x_eigen = eigen_matrix_solver->solve(A, b); + + // Check vector + Eigen::VectorXd check(dim); + check << 0.00953087, 0.00267145, 0.00090515, -0.0051681, -0.0118201, + -0.00524066, 0.00351178, -0.00226483, 0.00881954, 0.00402078, + -0.00374931, 0.00389581, 0.0278946, 0.00086471, 0.0108373, 0.00994061, + -0.0152397, -0.0131817, 0.00237895, 0.0107676, 0.0020594, 0.0167911, + -0.00424347, 0.000822635, 0.00284699, -0.000252711, 0.004012, + -0.0193392, -0.00497733, 0.00414727, -0.0191148, -0.00433217, + -0.00845599, 0.00625809, 0.00608814, 0.00172608, -0.0319379, + -0.0120693, -0.0199266, -0.0136787, 0.011165, 0.000247912, + -0.00396258, -0.00169115, 0.00979188, 0.0168016, 0.0106938, + -0.00551589, 0.0140779, 0.00212647; + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + SECTION("Eigen CG 50x50 sub solver type") { + // Change CG to lscg + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("lscg")); + + // Solve + x_eigen.setZero(); + x_eigen = eigen_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Change to bicgstab + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("bicgstab")); + + // Check for bi-cg-stab (expecting the results converge with more number + // of iterations (max_iter is 100)) + REQUIRE_NOTHROW(eigen_matrix_solver->set_max_iteration(120)); + + // Solve + x_eigen = eigen_matrix_solver->solve(A, b); + + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Change to error - unavailable subsolver type + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("error")); + + // Solve but x is zero size + x_eigen = eigen_matrix_solver->solve(A, b); + REQUIRE(x_eigen.size() == 0); + } + + SECTION("Eigen CG convergence settings") { + // Set tolerances and max iteration + REQUIRE_NOTHROW(eigen_matrix_solver->set_tolerance(1.e-7)); + REQUIRE_NOTHROW(eigen_matrix_solver->set_abs_tolerance(1.e-7)); + REQUIRE_NOTHROW(eigen_matrix_solver->set_div_tolerance(1.e-7)); + + // Reducing max iteration + REQUIRE_NOTHROW(eigen_matrix_solver->set_max_iteration(10)); + + // Solve + x_eigen.setZero(); + x_eigen = eigen_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) != Approx(check(i)).epsilon(Tolerance)); + + // Increasing max iteration + REQUIRE_NOTHROW(eigen_matrix_solver->set_max_iteration(100)); + + // Solve + x_eigen = eigen_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Set tolerances and max iteration to its limit + REQUIRE_NOTHROW(eigen_matrix_solver->set_tolerance( + std::numeric_limits::epsilon())); + REQUIRE_NOTHROW(eigen_matrix_solver->set_max_iteration(150)); + + // Solve + x_eigen = eigen_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(1.e-7)); + } + + SECTION("Eigen CG failure to converge") { + // Check fail + REQUIRE_NOTHROW(eigen_matrix_solver->set_tolerance(1.e-15)); + REQUIRE_NOTHROW(eigen_matrix_solver->set_max_iteration(10)); + + // Solve cg + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("cg")); + x_eigen.setZero(); + x_eigen = eigen_matrix_solver->solve(A, b); + + // Solve lscg + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("lscg")); + x_eigen = eigen_matrix_solver->solve(A, b); + + // Solve bicgstab + REQUIRE_NOTHROW(eigen_matrix_solver->set_sub_solver_type("bicgstab")); + x_eigen = eigen_matrix_solver->solve(A, b); + } + } + } + +#if USE_PETSC + SECTION("PETSC solver") { + std::shared_ptr>> + petsc_matrix_solver = + Factory>, unsigned, + double>::instance() + ->create("KrylovPETSC", std::move(max_iter), + std::move(rel_tolerance)); + + // Initiate sub_solver_type + std::string sub_solver_type = "cg"; + REQUIRE_NOTHROW(petsc_matrix_solver->set_sub_solver_type(sub_solver_type)); + + SECTION("PETSC CG 3x3 solver") { + std::vector mapper{0, 1, 2}; + petsc_matrix_solver->assign_global_active_dof(3); + petsc_matrix_solver->assign_rank_global_mapper(mapper); + + // Construct matrix and vector + const auto& A = CreateSymmetricTestMatrix3x3(); + Eigen::VectorXd b(3); + b << 6.0, 3.0, -9.0; + + // Solve + auto x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + REQUIRE(x_eigen(0) == Approx(2.).epsilon(Tolerance)); + REQUIRE(x_eigen(1) == Approx(1.).epsilon(Tolerance)); + REQUIRE(x_eigen(2) == Approx(-3.).epsilon(Tolerance)); + + SECTION("PETSC CG verbosity") { + // Verbosity 3 + REQUIRE_NOTHROW(petsc_matrix_solver->set_verbosity(3)); + + // Solve + x_eigen.setZero(); + x_eigen = petsc_matrix_solver->solve(A, b); + } + } + + SECTION("PETSC CG 5x5 solver") { + std::vector mapper{0, 1, 2, 3, 4}; + petsc_matrix_solver->assign_global_active_dof(5); + petsc_matrix_solver->assign_rank_global_mapper(mapper); + + // Construct matrix and vector + const auto& A = CreateRandomSymmetricTestMatrix(5); + const auto& b = CreateRandomRHSVector(5); + + // Solve + const auto& x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + REQUIRE(x_eigen(0) == Approx(0.0120451).epsilon(Tolerance)); + REQUIRE(x_eigen(1) == Approx(0.0059998).epsilon(Tolerance)); + REQUIRE(x_eigen(2) == Approx(-0.0002562).epsilon(Tolerance)); + REQUIRE(x_eigen(3) == Approx(-0.00243321).epsilon(Tolerance)); + REQUIRE(x_eigen(4) == Approx(-0.0137986).epsilon(Tolerance)); + } + + SECTION("PETSC CG 50x50 solver") { + unsigned dim = 50; + + std::vector mapper; + for (unsigned i = 0; i < dim; i++) mapper.push_back(i); + petsc_matrix_solver->assign_global_active_dof(dim); + petsc_matrix_solver->assign_rank_global_mapper(mapper); + + // Construct matrix and vector + const auto& A = CreateRandomSymmetricTestMatrix(dim, 1); + const auto& b = CreateRandomRHSVector(dim, 1); + + // Solve + auto x_eigen = petsc_matrix_solver->solve(A, b); + + // Check vector + Eigen::VectorXd check(dim); + check << 0.00953087, 0.00267145, 0.00090515, -0.0051681, -0.0118201, + -0.00524066, 0.00351178, -0.00226483, 0.00881954, 0.00402078, + -0.00374931, 0.00389581, 0.0278946, 0.00086471, 0.0108373, 0.00994061, + -0.0152397, -0.0131817, 0.00237895, 0.0107676, 0.0020594, 0.0167911, + -0.00424347, 0.000822635, 0.00284699, -0.000252711, 0.004012, + -0.0193392, -0.00497733, 0.00414727, -0.0191148, -0.00433217, + -0.00845599, 0.00625809, 0.00608814, 0.00172608, -0.0319379, + -0.0120693, -0.0199266, -0.0136787, 0.011165, 0.000247912, + -0.00396258, -0.00169115, 0.00979188, 0.0168016, 0.0106938, + -0.00551589, 0.0140779, 0.00212647; + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + SECTION("PETSC CG 50x50 sub solver type") { + // Change to gmres + REQUIRE_NOTHROW(petsc_matrix_solver->set_sub_solver_type("gmres")); + + // Solve + x_eigen.setZero(); + x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Change to bicgstab + REQUIRE_NOTHROW(petsc_matrix_solver->set_sub_solver_type("bicgstab")); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Change to lsqr + REQUIRE_NOTHROW(petsc_matrix_solver->set_sub_solver_type("lsqr")); + + // Solve - expected to diverge + x_eigen = petsc_matrix_solver->solve(A, b); + + // Change to error - unavailable subsolver type (set to gmres) + REQUIRE_NOTHROW(petsc_matrix_solver->set_sub_solver_type("error")); + REQUIRE_NOTHROW(petsc_matrix_solver->set_verbosity(1)); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + } + + SECTION("PETSC CG 50x50 preconditioner type") { + // Change to jacobi + REQUIRE_NOTHROW(petsc_matrix_solver->set_preconditioner_type("jacobi")); + + // Solve - expected diverge + x_eigen.setZero(); + x_eigen = petsc_matrix_solver->solve(A, b); + + // Change to block jacobi + REQUIRE_NOTHROW( + petsc_matrix_solver->set_preconditioner_type("bjacobi")); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Change to point block jacobi + REQUIRE_NOTHROW( + petsc_matrix_solver->set_preconditioner_type("pbjacobi")); + + // Solve - expected diverge + x_eigen = petsc_matrix_solver->solve(A, b); + + // Change to additive Schwarz method + REQUIRE_NOTHROW(petsc_matrix_solver->set_preconditioner_type("asm")); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Change to eisenstat + REQUIRE_NOTHROW( + petsc_matrix_solver->set_preconditioner_type("eisenstat")); + + // Solve - expected diverge + x_eigen = petsc_matrix_solver->solve(A, b); + + // Change to icc + REQUIRE_NOTHROW(petsc_matrix_solver->set_preconditioner_type("icc")); + + // Solve - expected diverge + x_eigen = petsc_matrix_solver->solve(A, b); + } + + SECTION("PETSC CG convergence settings") { + // Set tolerance + REQUIRE_NOTHROW(petsc_matrix_solver->set_tolerance(1.e-7)); + + // Solve + x_eigen.setZero(); + x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Set tolerances and max iteration to its limit + REQUIRE_NOTHROW(petsc_matrix_solver->set_tolerance( + std::numeric_limits::epsilon())); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Set abs_tolerance + REQUIRE_NOTHROW(petsc_matrix_solver->set_abs_tolerance( + std::numeric_limits::epsilon())); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Set abs_tolerance - expecting faster convergence + REQUIRE_NOTHROW(petsc_matrix_solver->set_abs_tolerance(1.e-7)); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + + // Set abs_tolerance - expecting to diverge + REQUIRE_NOTHROW(petsc_matrix_solver->set_div_tolerance( + std::numeric_limits::epsilon())); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + // Set abs_tolerance - expecting faster convergence (default div_tol + // is 1.e5) + REQUIRE_NOTHROW(petsc_matrix_solver->set_div_tolerance(1.e1)); + + // Solve + x_eigen = petsc_matrix_solver->solve(A, b); + + // Check + for (unsigned i = 0; i < dim; i++) + REQUIRE(x_eigen(i) == Approx(check(i)).epsilon(Tolerance)); + } + } + } +#endif +} \ No newline at end of file diff --git a/tests/mesh_free_surface_test.cc b/tests/mesh_free_surface_test.cc new file mode 100644 index 000000000..5d977fdde --- /dev/null +++ b/tests/mesh_free_surface_test.cc @@ -0,0 +1,1195 @@ +#include +#include + +#include "Eigen/Dense" +#include "catch.hpp" + +#include "cell.h" +#include "element.h" +#include "factory.h" +#include "hexahedron_element.h" +#include "hexahedron_quadrature.h" +#include "material.h" +#include "mesh.h" +#include "node.h" +#include "quadrilateral_element.h" +#include "quadrilateral_quadrature.h" + +TEST_CASE("Mesh free surface 2D", "[MeshCell][2D][free_surface]") { + // Dimension + const unsigned Dim = 2; + // Degrees of freedom + const unsigned Dof = 2; + // Number of phases + const unsigned Nphases = 1; + // Number of nodes per cell + const unsigned Nnodes = 4; + // Tolerance + const double Tolerance = 1.E-7; + + // Initialise material + Json jmaterial; + jmaterial["density"] = 1000.; + jmaterial["bulk_modulus"] = 8333333.333333333; + jmaterial["dynamic_viscosity"] = 8.9E-4; + auto material = + Factory, unsigned, const Json&>::instance()->create( + "Newtonian2D", std::move(0), jmaterial); + + auto mesh = std::make_shared>(0); + // Check mesh is active + REQUIRE(mesh->status() == false); + + // Coordinates + Eigen::Vector2d coords; + + coords << 0., 0.; + std::shared_ptr> node0 = + std::make_shared>(0, coords); + REQUIRE(mesh->add_node(node0) == true); + + coords << 2., 0.; + std::shared_ptr> node1 = + std::make_shared>(1, coords); + REQUIRE(mesh->add_node(node1) == true); + + coords << 4., 0.; + std::shared_ptr> node2 = + std::make_shared>(2, coords); + REQUIRE(mesh->add_node(node2) == true); + + coords << 6., 0.; + std::shared_ptr> node3 = + std::make_shared>(3, coords); + REQUIRE(mesh->add_node(node3) == true); + + coords << 8., 0.; + std::shared_ptr> node4 = + std::make_shared>(4, coords); + REQUIRE(mesh->add_node(node4) == true); + + coords << 10., 0.; + std::shared_ptr> node5 = + std::make_shared>(5, coords); + REQUIRE(mesh->add_node(node5) == true); + + coords << 0., 2.; + std::shared_ptr> node6 = + std::make_shared>(6, coords); + REQUIRE(mesh->add_node(node6) == true); + + coords << 2., 2.; + std::shared_ptr> node7 = + std::make_shared>(7, coords); + REQUIRE(mesh->add_node(node7) == true); + + coords << 4., 2.; + std::shared_ptr> node8 = + std::make_shared>(8, coords); + REQUIRE(mesh->add_node(node8) == true); + + coords << 6., 2.; + std::shared_ptr> node9 = + std::make_shared>(9, coords); + REQUIRE(mesh->add_node(node9) == true); + + coords << 8., 2.; + std::shared_ptr> node10 = + std::make_shared>(10, coords); + REQUIRE(mesh->add_node(node10) == true); + + coords << 10., 2.; + std::shared_ptr> node11 = + std::make_shared>(11, coords); + REQUIRE(mesh->add_node(node11) == true); + + coords << 0., 4.; + std::shared_ptr> node12 = + std::make_shared>(12, coords); + REQUIRE(mesh->add_node(node12) == true); + + coords << 2., 4.; + std::shared_ptr> node13 = + std::make_shared>(13, coords); + REQUIRE(mesh->add_node(node13) == true); + + coords << 4., 4.; + std::shared_ptr> node14 = + std::make_shared>(14, coords); + REQUIRE(mesh->add_node(node14) == true); + + coords << 6., 4.; + std::shared_ptr> node15 = + std::make_shared>(15, coords); + REQUIRE(mesh->add_node(node15) == true); + + coords << 8., 4.; + std::shared_ptr> node16 = + std::make_shared>(16, coords); + REQUIRE(mesh->add_node(node16) == true); + + coords << 10., 4.; + std::shared_ptr> node17 = + std::make_shared>(17, coords); + REQUIRE(mesh->add_node(node17) == true); + + coords << 0., 6.; + std::shared_ptr> node18 = + std::make_shared>(18, coords); + REQUIRE(mesh->add_node(node18) == true); + + coords << 2., 6.; + std::shared_ptr> node19 = + std::make_shared>(19, coords); + REQUIRE(mesh->add_node(node19) == true); + + coords << 4., 6.; + std::shared_ptr> node20 = + std::make_shared>(20, coords); + REQUIRE(mesh->add_node(node20) == true); + + coords << 6., 6.; + std::shared_ptr> node21 = + std::make_shared>(21, coords); + REQUIRE(mesh->add_node(node21) == true); + + coords << 8., 6.; + std::shared_ptr> node22 = + std::make_shared>(22, coords); + REQUIRE(mesh->add_node(node22) == true); + + coords << 10., 6.; + std::shared_ptr> node23 = + std::make_shared>(23, coords); + REQUIRE(mesh->add_node(node23) == true); + + coords << 0., 8.; + std::shared_ptr> node24 = + std::make_shared>(24, coords); + REQUIRE(mesh->add_node(node24) == true); + + coords << 2., 8.; + std::shared_ptr> node25 = + std::make_shared>(25, coords); + REQUIRE(mesh->add_node(node25) == true); + + coords << 4., 8.; + std::shared_ptr> node26 = + std::make_shared>(26, coords); + REQUIRE(mesh->add_node(node26) == true); + + coords << 6., 8.; + std::shared_ptr> node27 = + std::make_shared>(27, coords); + REQUIRE(mesh->add_node(node27) == true); + + coords << 8., 8.; + std::shared_ptr> node28 = + std::make_shared>(28, coords); + REQUIRE(mesh->add_node(node28) == true); + + coords << 10., 8.; + std::shared_ptr> node29 = + std::make_shared>(29, coords); + REQUIRE(mesh->add_node(node29) == true); + + coords << 0., 10.; + std::shared_ptr> node30 = + std::make_shared>(30, coords); + REQUIRE(mesh->add_node(node30) == true); + + coords << 2., 10.; + std::shared_ptr> node31 = + std::make_shared>(31, coords); + REQUIRE(mesh->add_node(node31) == true); + + coords << 4., 10.; + std::shared_ptr> node32 = + std::make_shared>(32, coords); + REQUIRE(mesh->add_node(node32) == true); + + coords << 6., 10.; + std::shared_ptr> node33 = + std::make_shared>(33, coords); + REQUIRE(mesh->add_node(node33) == true); + + coords << 8., 10.; + std::shared_ptr> node34 = + std::make_shared>(34, coords); + REQUIRE(mesh->add_node(node34) == true); + + coords << 10., 10.; + std::shared_ptr> node35 = + std::make_shared>(35, coords); + REQUIRE(mesh->add_node(node35) == true); + + // 4-noded quadrilateral shape functions + std::shared_ptr> element = + Factory>::instance()->create("ED2Q4"); + + mpm::Index id = 0; + auto cell0 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell0->add_node(0, node0) == true); + REQUIRE(cell0->add_node(1, node1) == true); + REQUIRE(cell0->add_node(2, node7) == true); + REQUIRE(cell0->add_node(3, node6) == true); + REQUIRE(cell0->nnodes() == 4); + + // Initialise cell and to mesh + REQUIRE(cell0->initialise() == true); + REQUIRE(mesh->add_cell(cell0) == true); + + id = 1; + auto cell1 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell1->add_node(0, node1) == true); + REQUIRE(cell1->add_node(1, node2) == true); + REQUIRE(cell1->add_node(2, node8) == true); + REQUIRE(cell1->add_node(3, node7) == true); + REQUIRE(cell1->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell1->initialise() == true); + REQUIRE(mesh->add_cell(cell1) == true); + + id = 2; + auto cell2 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell2->add_node(0, node2) == true); + REQUIRE(cell2->add_node(1, node3) == true); + REQUIRE(cell2->add_node(2, node9) == true); + REQUIRE(cell2->add_node(3, node8) == true); + REQUIRE(cell2->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell2->initialise() == true); + REQUIRE(mesh->add_cell(cell2) == true); + + id = 3; + auto cell3 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell3->add_node(0, node3) == true); + REQUIRE(cell3->add_node(1, node4) == true); + REQUIRE(cell3->add_node(2, node10) == true); + REQUIRE(cell3->add_node(3, node9) == true); + REQUIRE(cell3->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell3->initialise() == true); + REQUIRE(mesh->add_cell(cell3) == true); + + id = 4; + auto cell4 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell4->add_node(0, node4) == true); + REQUIRE(cell4->add_node(1, node5) == true); + REQUIRE(cell4->add_node(2, node11) == true); + REQUIRE(cell4->add_node(3, node10) == true); + REQUIRE(cell4->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell4->initialise() == true); + REQUIRE(mesh->add_cell(cell4) == true); + + id = 5; + auto cell5 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell5->add_node(0, node6) == true); + REQUIRE(cell5->add_node(1, node7) == true); + REQUIRE(cell5->add_node(2, node13) == true); + REQUIRE(cell5->add_node(3, node12) == true); + REQUIRE(cell5->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell5->initialise() == true); + REQUIRE(mesh->add_cell(cell5) == true); + + id = 6; + auto cell6 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell6->add_node(0, node7) == true); + REQUIRE(cell6->add_node(1, node8) == true); + REQUIRE(cell6->add_node(2, node14) == true); + REQUIRE(cell6->add_node(3, node13) == true); + REQUIRE(cell6->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell6->initialise() == true); + REQUIRE(mesh->add_cell(cell6) == true); + + id = 7; + auto cell7 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell7->add_node(0, node8) == true); + REQUIRE(cell7->add_node(1, node9) == true); + REQUIRE(cell7->add_node(2, node15) == true); + REQUIRE(cell7->add_node(3, node14) == true); + REQUIRE(cell7->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell7->initialise() == true); + REQUIRE(mesh->add_cell(cell7) == true); + + id = 8; + auto cell8 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell8->add_node(0, node9) == true); + REQUIRE(cell8->add_node(1, node10) == true); + REQUIRE(cell8->add_node(2, node16) == true); + REQUIRE(cell8->add_node(3, node15) == true); + REQUIRE(cell8->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell8->initialise() == true); + REQUIRE(mesh->add_cell(cell8) == true); + + id = 9; + auto cell9 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell9->add_node(0, node10) == true); + REQUIRE(cell9->add_node(1, node11) == true); + REQUIRE(cell9->add_node(2, node17) == true); + REQUIRE(cell9->add_node(3, node16) == true); + REQUIRE(cell9->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell9->initialise() == true); + REQUIRE(mesh->add_cell(cell9) == true); + + id = 10; + auto cell10 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell10->add_node(0, node12) == true); + REQUIRE(cell10->add_node(1, node13) == true); + REQUIRE(cell10->add_node(2, node19) == true); + REQUIRE(cell10->add_node(3, node18) == true); + REQUIRE(cell10->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell10->initialise() == true); + REQUIRE(mesh->add_cell(cell10) == true); + + id = 11; + auto cell11 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell11->add_node(0, node13) == true); + REQUIRE(cell11->add_node(1, node14) == true); + REQUIRE(cell11->add_node(2, node20) == true); + REQUIRE(cell11->add_node(3, node19) == true); + REQUIRE(cell11->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell11->initialise() == true); + REQUIRE(mesh->add_cell(cell11) == true); + + id = 12; + auto cell12 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell12->add_node(0, node14) == true); + REQUIRE(cell12->add_node(1, node15) == true); + REQUIRE(cell12->add_node(2, node21) == true); + REQUIRE(cell12->add_node(3, node20) == true); + REQUIRE(cell12->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell12->initialise() == true); + REQUIRE(mesh->add_cell(cell12) == true); + + id = 13; + auto cell13 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell13->add_node(0, node15) == true); + REQUIRE(cell13->add_node(1, node16) == true); + REQUIRE(cell13->add_node(2, node22) == true); + REQUIRE(cell13->add_node(3, node21) == true); + REQUIRE(cell13->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell13->initialise() == true); + REQUIRE(mesh->add_cell(cell13) == true); + + id = 14; + auto cell14 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell14->add_node(0, node16) == true); + REQUIRE(cell14->add_node(1, node17) == true); + REQUIRE(cell14->add_node(2, node23) == true); + REQUIRE(cell14->add_node(3, node22) == true); + REQUIRE(cell14->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell14->initialise() == true); + REQUIRE(mesh->add_cell(cell14) == true); + + id = 15; + auto cell15 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell15->add_node(0, node18) == true); + REQUIRE(cell15->add_node(1, node19) == true); + REQUIRE(cell15->add_node(2, node25) == true); + REQUIRE(cell15->add_node(3, node24) == true); + REQUIRE(cell15->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell15->initialise() == true); + REQUIRE(mesh->add_cell(cell15) == true); + + id = 16; + auto cell16 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell16->add_node(0, node19) == true); + REQUIRE(cell16->add_node(1, node20) == true); + REQUIRE(cell16->add_node(2, node26) == true); + REQUIRE(cell16->add_node(3, node25) == true); + REQUIRE(cell16->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell16->initialise() == true); + REQUIRE(mesh->add_cell(cell16) == true); + + id = 17; + auto cell17 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell17->add_node(0, node20) == true); + REQUIRE(cell17->add_node(1, node21) == true); + REQUIRE(cell17->add_node(2, node27) == true); + REQUIRE(cell17->add_node(3, node26) == true); + REQUIRE(cell17->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell17->initialise() == true); + REQUIRE(mesh->add_cell(cell17) == true); + + id = 18; + auto cell18 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell18->add_node(0, node21) == true); + REQUIRE(cell18->add_node(1, node22) == true); + REQUIRE(cell18->add_node(2, node28) == true); + REQUIRE(cell18->add_node(3, node27) == true); + REQUIRE(cell18->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell18->initialise() == true); + REQUIRE(mesh->add_cell(cell18) == true); + + id = 19; + auto cell19 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell19->add_node(0, node22) == true); + REQUIRE(cell19->add_node(1, node23) == true); + REQUIRE(cell19->add_node(2, node29) == true); + REQUIRE(cell19->add_node(3, node28) == true); + REQUIRE(cell19->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell19->initialise() == true); + REQUIRE(mesh->add_cell(cell19) == true); + + id = 20; + auto cell20 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell20->add_node(0, node24) == true); + REQUIRE(cell20->add_node(1, node25) == true); + REQUIRE(cell20->add_node(2, node31) == true); + REQUIRE(cell20->add_node(3, node30) == true); + REQUIRE(cell20->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell20->initialise() == true); + REQUIRE(mesh->add_cell(cell20) == true); + + id = 21; + auto cell21 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell21->add_node(0, node25) == true); + REQUIRE(cell21->add_node(1, node26) == true); + REQUIRE(cell21->add_node(2, node32) == true); + REQUIRE(cell21->add_node(3, node31) == true); + REQUIRE(cell21->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell21->initialise() == true); + REQUIRE(mesh->add_cell(cell21) == true); + + id = 22; + auto cell22 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell22->add_node(0, node26) == true); + REQUIRE(cell22->add_node(1, node27) == true); + REQUIRE(cell22->add_node(2, node33) == true); + REQUIRE(cell22->add_node(3, node32) == true); + REQUIRE(cell22->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell22->initialise() == true); + REQUIRE(mesh->add_cell(cell22) == true); + + id = 23; + auto cell23 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell23->add_node(0, node27) == true); + REQUIRE(cell23->add_node(1, node28) == true); + REQUIRE(cell23->add_node(2, node34) == true); + REQUIRE(cell23->add_node(3, node33) == true); + REQUIRE(cell23->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell23->initialise() == true); + REQUIRE(mesh->add_cell(cell23) == true); + + id = 24; + auto cell24 = std::make_shared>(id, Nnodes, element); + + REQUIRE(cell24->add_node(0, node28) == true); + REQUIRE(cell24->add_node(1, node29) == true); + REQUIRE(cell24->add_node(2, node35) == true); + REQUIRE(cell24->add_node(3, node34) == true); + REQUIRE(cell24->nnodes() == 4); + + // Initialise cell and add to mesh + REQUIRE(cell24->initialise() == true); + REQUIRE(mesh->add_cell(cell24) == true); + + // Find cell neighbours + mesh->find_cell_neighbours(); + + std::shared_ptr> particle0, particle1, particle2, + particle3, particle4, particle5, particle6, particle7, particle8, + particle9, particle10, particle11, particle12, particle13, particle14, + particle15; + + coords << 3.5, 3.5; + particle0 = std::make_shared>(0, coords); + REQUIRE(mesh->add_particle(particle0, false) == true); + + coords << 4.5, 3.5; + particle1 = std::make_shared>(1, coords); + REQUIRE(mesh->add_particle(particle1, false) == true); + + coords << 5.5, 3.5; + particle2 = std::make_shared>(2, coords); + REQUIRE(mesh->add_particle(particle2, false) == true); + + coords << 6.5, 3.5; + particle3 = std::make_shared>(3, coords); + REQUIRE(mesh->add_particle(particle3, false) == true); + + coords << 3.5, 4.5; + particle4 = std::make_shared>(4, coords); + REQUIRE(mesh->add_particle(particle4, false) == true); + + coords << 4.5, 4.5; + particle5 = std::make_shared>(5, coords); + REQUIRE(mesh->add_particle(particle5, false) == true); + + coords << 5.5, 4.5; + particle6 = std::make_shared>(6, coords); + REQUIRE(mesh->add_particle(particle6, false) == true); + + coords << 6.5, 4.5; + particle7 = std::make_shared>(7, coords); + REQUIRE(mesh->add_particle(particle7, false) == true); + + coords << 3.5, 5.5; + particle8 = std::make_shared>(8, coords); + REQUIRE(mesh->add_particle(particle8, false) == true); + + coords << 4.5, 5.5; + particle9 = std::make_shared>(9, coords); + REQUIRE(mesh->add_particle(particle9, false) == true); + + coords << 5.5, 5.5; + particle10 = std::make_shared>(10, coords); + REQUIRE(mesh->add_particle(particle10, false) == true); + + coords << 6.5, 5.5; + particle11 = std::make_shared>(11, coords); + REQUIRE(mesh->add_particle(particle11, false) == true); + + coords << 3.5, 6.5; + particle12 = std::make_shared>(12, coords); + REQUIRE(mesh->add_particle(particle12, false) == true); + + coords << 4.5, 6.5; + particle13 = std::make_shared>(13, coords); + REQUIRE(mesh->add_particle(particle13, false) == true); + + coords << 5.5, 6.5; + particle14 = std::make_shared>(14, coords); + REQUIRE(mesh->add_particle(particle14, false) == true); + + coords << 6.5, 6.5; + particle15 = std::make_shared>(15, coords); + REQUIRE(mesh->add_particle(particle15, false) == true); + + // Assign material to particles + mesh->iterate_over_particles( + std::bind(&mpm::ParticleBase::assign_material, std::placeholders::_1, + material, 0)); + + // Locate particles in a mesh + auto particles = mesh->locate_particles_mesh(); + + // Should find all particles in mesh + REQUIRE(particles.size() == 0); + + // Check particles inside cells + REQUIRE(cell0->nparticles() == 0); + REQUIRE(cell1->nparticles() == 0); + REQUIRE(cell2->nparticles() == 0); + REQUIRE(cell3->nparticles() == 0); + REQUIRE(cell4->nparticles() == 0); + REQUIRE(cell5->nparticles() == 0); + REQUIRE(cell6->nparticles() == 1); + REQUIRE(cell7->nparticles() == 2); + REQUIRE(cell8->nparticles() == 1); + REQUIRE(cell9->nparticles() == 0); + REQUIRE(cell10->nparticles() == 0); + REQUIRE(cell11->nparticles() == 2); + REQUIRE(cell12->nparticles() == 4); + REQUIRE(cell13->nparticles() == 2); + REQUIRE(cell14->nparticles() == 0); + REQUIRE(cell15->nparticles() == 0); + REQUIRE(cell16->nparticles() == 1); + REQUIRE(cell17->nparticles() == 2); + REQUIRE(cell18->nparticles() == 1); + REQUIRE(cell19->nparticles() == 0); + REQUIRE(cell20->nparticles() == 0); + REQUIRE(cell21->nparticles() == 0); + REQUIRE(cell22->nparticles() == 0); + REQUIRE(cell23->nparticles() == 0); + REQUIRE(cell24->nparticles() == 0); + + // Find particle neighbours + mesh->find_particle_neighbours(); + + // Check particle neighbours + REQUIRE(particle0->nneighbours() == 8); + REQUIRE(particle1->nneighbours() == 11); + REQUIRE(particle2->nneighbours() == 11); + REQUIRE(particle3->nneighbours() == 8); + REQUIRE(particle4->nneighbours() == 11); + REQUIRE(particle5->nneighbours() == 15); + REQUIRE(particle6->nneighbours() == 15); + REQUIRE(particle7->nneighbours() == 11); + REQUIRE(particle8->nneighbours() == 11); + REQUIRE(particle9->nneighbours() == 15); + REQUIRE(particle10->nneighbours() == 15); + REQUIRE(particle11->nneighbours() == 11); + REQUIRE(particle12->nneighbours() == 8); + REQUIRE(particle13->nneighbours() == 11); + REQUIRE(particle14->nneighbours() == 11); + REQUIRE(particle15->nneighbours() == 8); + + // Initialise particle variables + // Assign particle volume + mesh->iterate_over_particles(std::bind(&mpm::ParticleBase::assign_volume, + std::placeholders::_1, 1.)); + + // Compute mass + mesh->iterate_over_particles( + std::bind(&mpm::ParticleBase::compute_mass, std::placeholders::_1)); + + // Initialise nodes + mesh->iterate_over_nodes( + std::bind(&mpm::NodeBase::initialise, std::placeholders::_1)); + + mesh->iterate_over_cells( + std::bind(&mpm::Cell::activate_nodes, std::placeholders::_1)); + + // Iterate over each particle to compute shapefn + mesh->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_shapefn, std::placeholders::_1)); + + // Assign mass and momentum to nodes + mesh->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_mass_momentum_to_nodes, + std::placeholders::_1)); + + SECTION("Mesh check initial condition") { + + // Check cell free surface + REQUIRE(cell0->free_surface() == false); + REQUIRE(cell1->free_surface() == false); + REQUIRE(cell2->free_surface() == false); + REQUIRE(cell3->free_surface() == false); + REQUIRE(cell4->free_surface() == false); + REQUIRE(cell5->free_surface() == false); + REQUIRE(cell6->free_surface() == false); + REQUIRE(cell7->free_surface() == false); + REQUIRE(cell8->free_surface() == false); + REQUIRE(cell9->free_surface() == false); + REQUIRE(cell10->free_surface() == false); + REQUIRE(cell11->free_surface() == false); + REQUIRE(cell12->free_surface() == false); + REQUIRE(cell13->free_surface() == false); + REQUIRE(cell14->free_surface() == false); + REQUIRE(cell15->free_surface() == false); + REQUIRE(cell16->free_surface() == false); + REQUIRE(cell17->free_surface() == false); + REQUIRE(cell18->free_surface() == false); + REQUIRE(cell19->free_surface() == false); + REQUIRE(cell20->free_surface() == false); + REQUIRE(cell21->free_surface() == false); + REQUIRE(cell22->free_surface() == false); + REQUIRE(cell23->free_surface() == false); + REQUIRE(cell24->free_surface() == false); + + // Check cell status + REQUIRE(cell0->status() == false); + REQUIRE(cell1->status() == false); + REQUIRE(cell2->status() == false); + REQUIRE(cell3->status() == false); + REQUIRE(cell4->status() == false); + REQUIRE(cell5->status() == false); + REQUIRE(cell6->status() == true); + REQUIRE(cell7->status() == true); + REQUIRE(cell8->status() == true); + REQUIRE(cell9->status() == false); + REQUIRE(cell10->status() == false); + REQUIRE(cell11->status() == true); + REQUIRE(cell12->status() == true); + REQUIRE(cell13->status() == true); + REQUIRE(cell14->status() == false); + REQUIRE(cell15->status() == false); + REQUIRE(cell16->status() == true); + REQUIRE(cell17->status() == true); + REQUIRE(cell18->status() == true); + REQUIRE(cell19->status() == false); + REQUIRE(cell20->status() == false); + REQUIRE(cell21->status() == false); + REQUIRE(cell22->status() == false); + REQUIRE(cell23->status() == false); + REQUIRE(cell24->status() == false); + + // Check node free surface + REQUIRE(node0->free_surface() == false); + REQUIRE(node1->free_surface() == false); + REQUIRE(node2->free_surface() == false); + REQUIRE(node3->free_surface() == false); + REQUIRE(node4->free_surface() == false); + REQUIRE(node5->free_surface() == false); + REQUIRE(node6->free_surface() == false); + REQUIRE(node7->free_surface() == false); + REQUIRE(node8->free_surface() == false); + REQUIRE(node9->free_surface() == false); + REQUIRE(node10->free_surface() == false); + REQUIRE(node11->free_surface() == false); + REQUIRE(node12->free_surface() == false); + REQUIRE(node13->free_surface() == false); + REQUIRE(node14->free_surface() == false); + REQUIRE(node15->free_surface() == false); + REQUIRE(node16->free_surface() == false); + REQUIRE(node17->free_surface() == false); + REQUIRE(node18->free_surface() == false); + REQUIRE(node19->free_surface() == false); + REQUIRE(node20->free_surface() == false); + REQUIRE(node21->free_surface() == false); + REQUIRE(node22->free_surface() == false); + REQUIRE(node23->free_surface() == false); + REQUIRE(node24->free_surface() == false); + REQUIRE(node25->free_surface() == false); + REQUIRE(node26->free_surface() == false); + REQUIRE(node27->free_surface() == false); + REQUIRE(node28->free_surface() == false); + REQUIRE(node29->free_surface() == false); + REQUIRE(node30->free_surface() == false); + REQUIRE(node31->free_surface() == false); + REQUIRE(node32->free_surface() == false); + REQUIRE(node33->free_surface() == false); + REQUIRE(node34->free_surface() == false); + REQUIRE(node35->free_surface() == false); + + // Check node status + REQUIRE(node0->status() == false); + REQUIRE(node1->status() == false); + REQUIRE(node2->status() == false); + REQUIRE(node3->status() == false); + REQUIRE(node4->status() == false); + REQUIRE(node5->status() == false); + REQUIRE(node6->status() == false); + REQUIRE(node7->status() == true); + REQUIRE(node8->status() == true); + REQUIRE(node9->status() == true); + REQUIRE(node10->status() == true); + REQUIRE(node11->status() == false); + REQUIRE(node12->status() == false); + REQUIRE(node13->status() == true); + REQUIRE(node14->status() == true); + REQUIRE(node15->status() == true); + REQUIRE(node16->status() == true); + REQUIRE(node17->status() == false); + REQUIRE(node18->status() == false); + REQUIRE(node19->status() == true); + REQUIRE(node20->status() == true); + REQUIRE(node21->status() == true); + REQUIRE(node22->status() == true); + REQUIRE(node23->status() == false); + REQUIRE(node24->status() == false); + REQUIRE(node25->status() == true); + REQUIRE(node26->status() == true); + REQUIRE(node27->status() == true); + REQUIRE(node28->status() == true); + REQUIRE(node29->status() == false); + REQUIRE(node30->status() == false); + REQUIRE(node31->status() == false); + REQUIRE(node32->status() == false); + REQUIRE(node33->status() == false); + REQUIRE(node34->status() == false); + REQUIRE(node35->status() == false); + + // Check particle free surface + REQUIRE(particle0->free_surface() == false); + REQUIRE(particle1->free_surface() == false); + REQUIRE(particle2->free_surface() == false); + REQUIRE(particle3->free_surface() == false); + REQUIRE(particle4->free_surface() == false); + REQUIRE(particle5->free_surface() == false); + REQUIRE(particle6->free_surface() == false); + REQUIRE(particle7->free_surface() == false); + REQUIRE(particle8->free_surface() == false); + REQUIRE(particle9->free_surface() == false); + REQUIRE(particle10->free_surface() == false); + REQUIRE(particle11->free_surface() == false); + REQUIRE(particle12->free_surface() == false); + REQUIRE(particle13->free_surface() == false); + REQUIRE(particle14->free_surface() == false); + REQUIRE(particle15->free_surface() == false); + } + + // Check solutions + std::set fsc = {6, 7, 8, 11, 13, 16, 17, 18}; + std::set fsn = {7, 8, 9, 10, 13, 16, 19, 22, 25, 26, 27, 28}; + std::set fsp = {0, 1, 2, 3, 4, 7, 8, 11, 12, 13, 14, 15}; + + SECTION("Mesh free surface 2D by density") { + + REQUIRE(mesh->compute_free_surface_by_density(0.25) == true); + + // Check cell volume fraction + REQUIRE(cell0->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell1->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell2->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell3->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell4->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell5->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell6->volume_fraction() == Approx(0.25).epsilon(Tolerance)); + REQUIRE(cell7->volume_fraction() == Approx(0.5).epsilon(Tolerance)); + REQUIRE(cell8->volume_fraction() == Approx(0.25).epsilon(Tolerance)); + REQUIRE(cell9->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell10->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell11->volume_fraction() == Approx(0.5).epsilon(Tolerance)); + REQUIRE(cell12->volume_fraction() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(cell13->volume_fraction() == Approx(0.5).epsilon(Tolerance)); + REQUIRE(cell14->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell15->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell16->volume_fraction() == Approx(0.25).epsilon(Tolerance)); + REQUIRE(cell17->volume_fraction() == Approx(0.5).epsilon(Tolerance)); + REQUIRE(cell18->volume_fraction() == Approx(0.25).epsilon(Tolerance)); + REQUIRE(cell19->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell20->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell21->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell22->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell23->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(cell24->volume_fraction() == Approx(0.0).epsilon(Tolerance)); + + // Check nodal density for active nodes + REQUIRE(node7->density(0) == Approx(62.5).epsilon(Tolerance)); + REQUIRE(node8->density(0) == Approx(218.75).epsilon(Tolerance)); + REQUIRE(node9->density(0) == Approx(218.75).epsilon(Tolerance)); + REQUIRE(node10->density(0) == Approx(62.5).epsilon(Tolerance)); + + REQUIRE(node13->density(0) == Approx(218.75).epsilon(Tolerance)); + REQUIRE(node14->density(0) == Approx(765.625).epsilon(Tolerance)); + REQUIRE(node15->density(0) == Approx(765.625).epsilon(Tolerance)); + REQUIRE(node16->density(0) == Approx(218.75).epsilon(Tolerance)); + + REQUIRE(node19->density(0) == Approx(218.75).epsilon(Tolerance)); + REQUIRE(node20->density(0) == Approx(765.625).epsilon(Tolerance)); + REQUIRE(node21->density(0) == Approx(765.625).epsilon(Tolerance)); + REQUIRE(node22->density(0) == Approx(218.75).epsilon(Tolerance)); + + REQUIRE(node25->density(0) == Approx(62.5).epsilon(Tolerance)); + REQUIRE(node26->density(0) == Approx(218.75).epsilon(Tolerance)); + REQUIRE(node27->density(0) == Approx(218.75).epsilon(Tolerance)); + REQUIRE(node28->density(0) == Approx(62.5).epsilon(Tolerance)); + + // Check cell, node, and particle free surface + REQUIRE(mesh->free_surface_cells() == fsc); + REQUIRE(mesh->free_surface_nodes() == fsn); + REQUIRE(mesh->free_surface_particles() == fsp); + } + + SECTION("Mesh free surface 2D by geometry") { + + REQUIRE(mesh->compute_free_surface_by_geometry(0.25) == true); + + // Check cell, node, and particle free surface + REQUIRE(mesh->free_surface_cells() == fsc); + REQUIRE(mesh->free_surface_nodes() == fsn); + REQUIRE(mesh->free_surface_particles() == fsp); + + // Check particle normal vector + REQUIRE(particle0->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle1->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle2->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle3->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle4->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle5->normal().norm() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(particle6->normal().norm() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(particle7->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle8->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle9->normal().norm() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(particle10->normal().norm() == Approx(0.0).epsilon(Tolerance)); + REQUIRE(particle11->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle12->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle13->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle14->normal().norm() == Approx(1.0).epsilon(Tolerance)); + REQUIRE(particle15->normal().norm() == Approx(1.0).epsilon(Tolerance)); + } + + SECTION("Mesh free surface 2D") { + + std::string method = "density"; + REQUIRE(mesh->compute_free_surface(method, 0.25) == true); + + // Check cell, node, and particle free surface + REQUIRE(mesh->free_surface_cells() == fsc); + REQUIRE(mesh->free_surface_nodes() == fsn); + REQUIRE(mesh->free_surface_particles() == fsp); + + method = "geometry"; + REQUIRE(mesh->compute_free_surface(method, 0.25) == true); + + // Check cell, node, and particle free surface + REQUIRE(mesh->free_surface_cells() == fsc); + REQUIRE(mesh->free_surface_nodes() == fsn); + REQUIRE(mesh->free_surface_particles() == fsp); + + method = "other"; + REQUIRE(mesh->compute_free_surface(method, 0.25) == true); + } +} + +TEST_CASE("Mesh free surface 3D", "[MeshCell][3D][free_surface]") { + // Dimension + const unsigned Dim = 3; + // Degrees of freedom + const unsigned Dof = 3; + // Number of phases + const unsigned Nphases = 1; + // Number of nodes per cell + const unsigned Nnodes = 8; + // Tolerance + const double Tolerance = 1.E-7; + + // Initialise material + Json jmaterial; + jmaterial["density"] = 1000.; + jmaterial["bulk_modulus"] = 8333333.333333333; + jmaterial["dynamic_viscosity"] = 8.9E-4; + auto material = + Factory, unsigned, const Json&>::instance()->create( + "Newtonian3D", std::move(0), jmaterial); + + auto mesh = std::make_shared>(0); + // Check mesh is active + REQUIRE(mesh->status() == false); + + // Create nodes + Eigen::Vector3d coords; + mpm::Index id = 0; + double mesh_size = 2.; + for (unsigned k = 0; k < 6; k++) { + for (unsigned j = 0; j < 6; j++) { + for (unsigned i = 0; i < 6; i++) { + coords << double(i * mesh_size), double(j * mesh_size), + double(k * mesh_size); + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(mesh->add_node(node) == true); + id++; + } + } + } + + REQUIRE(mesh->nnodes() == 216); + + // 8-noded hexahedral shape functions + std::shared_ptr> element = + Factory>::instance()->create("ED3H8"); + + // Create cells + id = 0; + for (unsigned k = 0; k < 5; k++) { + for (unsigned j = 0; j < 5; j++) { + for (unsigned i = 0; i < 5; i++) { + auto node_0 = mesh->node(k * 36 + j * 6 + i); + auto node_1 = mesh->node(k * 36 + j * 6 + i + 1); + auto node_2 = mesh->node(k * 36 + (j + 1) * 6 + i + 1); + auto node_3 = mesh->node(k * 36 + (j + 1) * 6 + i); + + auto node_4 = mesh->node((k + 1) * 36 + j * 6 + i); + auto node_5 = mesh->node((k + 1) * 36 + j * 6 + i + 1); + auto node_6 = mesh->node((k + 1) * 36 + (j + 1) * 6 + i + 1); + auto node_7 = mesh->node((k + 1) * 36 + (j + 1) * 6 + i); + + auto cell = std::make_shared>(id, Nnodes, element); + + cell->add_node(0, node_0); + cell->add_node(1, node_1); + cell->add_node(2, node_2); + cell->add_node(3, node_3); + cell->add_node(4, node_4); + cell->add_node(5, node_5); + cell->add_node(6, node_6); + cell->add_node(7, node_7); + REQUIRE(cell->nnodes() == 8); + + // Initialise cell and add to mesh + REQUIRE(cell->initialise() == true); + REQUIRE(mesh->add_cell(cell) == true); + + id++; + } + } + } + + REQUIRE(mesh->ncells() == 125); + + // Find cell neighbours + mesh->find_cell_neighbours(); + + // Create particles + id = 0; + double particle_size = 1.; + Eigen::Vector3d base_coords; + base_coords << 3.5, 3.5, 3.5; + for (unsigned k = 0; k < 4; k++) { + for (unsigned j = 0; j < 4; j++) { + for (unsigned i = 0; i < 4; i++) { + coords << double(i * particle_size), double(j * particle_size), + double(k * particle_size); + std::shared_ptr> particle = + std::make_shared>(id, base_coords + coords); + REQUIRE(mesh->add_particle(particle, false) == true); + id++; + } + } + } + + REQUIRE(mesh->nparticles() == 64); + + // Assign material to particles + mesh->iterate_over_particles( + std::bind(&mpm::ParticleBase::assign_material, std::placeholders::_1, + material, 0)); + + // Locate particles in a mesh + auto particles = mesh->locate_particles_mesh(); + + // Should find all particles in mesh + REQUIRE(particles.size() == 0); + + // Find particle neighbours + mesh->find_particle_neighbours(); + + // Initialise particle variables + // Assign particle volume + mesh->iterate_over_particles(std::bind(&mpm::ParticleBase::assign_volume, + std::placeholders::_1, 1.)); + + // Compute mass + mesh->iterate_over_particles( + std::bind(&mpm::ParticleBase::compute_mass, std::placeholders::_1)); + + // Initialise nodes + mesh->iterate_over_nodes( + std::bind(&mpm::NodeBase::initialise, std::placeholders::_1)); + + mesh->iterate_over_cells( + std::bind(&mpm::Cell::activate_nodes, std::placeholders::_1)); + + // Iterate over each particle to compute shapefn + mesh->iterate_over_particles(std::bind( + &mpm::ParticleBase::compute_shapefn, std::placeholders::_1)); + + // Assign mass and momentum to nodes + mesh->iterate_over_particles( + std::bind(&mpm::ParticleBase::map_mass_momentum_to_nodes, + std::placeholders::_1)); + + // Check solutions + std::set fsc = {31, 32, 33, 36, 37, 38, 41, 42, 43, + 56, 57, 58, 61, 63, 66, 67, 68, 81, + 82, 83, 86, 87, 88, 91, 92, 93}; + std::set fsn = { + 43, 44, 45, 46, 49, 50, 51, 52, 55, 56, 57, 58, 61, 62, + 63, 64, 79, 80, 81, 82, 85, 88, 91, 94, 97, 98, 99, 100, + 115, 116, 117, 118, 121, 124, 127, 130, 133, 134, 135, 136, 151, 152, + 153, 154, 157, 158, 159, 160, 163, 164, 165, 166, 169, 170, 171, 172}; + std::set fsp = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 23, 24, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 39, 40, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63}; + + SECTION("Mesh free surface 3D by density") { + // Check initial free surface + REQUIRE(mesh->free_surface_cells().size() == 0); + REQUIRE(mesh->free_surface_nodes().size() == 0); + REQUIRE(mesh->free_surface_particles().size() == 0); + + REQUIRE(mesh->compute_free_surface_by_density(0.125) == true); + + // Check cell, node, and particle free surface + REQUIRE(mesh->free_surface_cells() == fsc); + REQUIRE(mesh->free_surface_nodes() == fsn); + REQUIRE(mesh->free_surface_particles() == fsp); + } + + SECTION("Mesh free surface 3D by geometry") { + + REQUIRE(mesh->compute_free_surface_by_geometry(0.125) == true); + + // Check cell, node, and particle free surface + REQUIRE(mesh->free_surface_cells() == fsc); + REQUIRE(mesh->free_surface_nodes() == fsn); + REQUIRE(mesh->free_surface_particles() == fsp); + + // Check particle normal vector + auto particle_normal = mesh->particles_vector_data("normals"); + for (unsigned fs_id = 0; fs_id < particle_normal.size(); fs_id++) { + if (fsp.find(fs_id) != fsp.end()) + REQUIRE(particle_normal[fs_id].norm() == + Approx(1.0).epsilon(Tolerance)); + else + REQUIRE(particle_normal[fs_id].norm() == + Approx(0.0).epsilon(Tolerance)); + } + } + + SECTION("Mesh free surface 3D") { + + std::string method = "density"; + REQUIRE(mesh->compute_free_surface(method, 0.125) == true); + + // Check cell, node, and particle free surface + REQUIRE(mesh->free_surface_cells() == fsc); + REQUIRE(mesh->free_surface_nodes() == fsn); + REQUIRE(mesh->free_surface_particles() == fsp); + + method = "geometry"; + REQUIRE(mesh->compute_free_surface(method, 0.125) == true); + + // Check cell, node, and particle free surface + REQUIRE(mesh->free_surface_cells() == fsc); + REQUIRE(mesh->free_surface_nodes() == fsn); + REQUIRE(mesh->free_surface_particles() == fsp); + + method = "other"; + REQUIRE(mesh->compute_free_surface(method, 0.125) == true); + } +} diff --git a/tests/mesh_test_2d.cc b/tests/mesh_test_2d.cc index eedc61044..791b30aaa 100644 --- a/tests/mesh_test_2d.cc +++ b/tests/mesh_test_2d.cc @@ -187,6 +187,7 @@ TEST_CASE("Mesh is checked for 2D case", "[mesh][2D]") { REQUIRE(mesh->status() == true); // Check number of particles in mesh REQUIRE(mesh->nparticles() == 2); + REQUIRE(mesh->nparticles("P2D") == 2); // Update coordinates Eigen::Vector2d coordinates; @@ -217,11 +218,13 @@ TEST_CASE("Mesh is checked for 2D case", "[mesh][2D]") { REQUIRE(mesh->remove_particle(particle2) == true); // Check number of particles in mesh REQUIRE(mesh->nparticles() == 1); + REQUIRE(mesh->nparticles("P2D") == 1); // Remove all non-rank particles in mesh mesh->remove_all_nonrank_particles(); // Check number of particles in mesh REQUIRE(mesh->nparticles() == 0); + REQUIRE(mesh->nparticles("P2D") == 0); // Add and use remove all particles REQUIRE(mesh->add_particle(particle1) == true); @@ -906,7 +909,7 @@ TEST_CASE("Mesh is checked for 2D case", "[mesh][2D]") { // Test HDF5 SECTION("Write particles HDF5") { - REQUIRE(mesh->write_particles_hdf5(0, "particles-2d.h5") == true); + REQUIRE(mesh->write_particles_hdf5("particles-2d.h5") == true); auto phdf5 = mesh->particles_hdf5(); REQUIRE(phdf5.size() == mesh->nparticles()); @@ -1150,6 +1153,27 @@ TEST_CASE("Mesh is checked for 2D case", "[mesh][2D]") { set_id, friction_constraint) == false); } + SECTION("Check assign pressure constraints to nodes") { + tsl::robin_map> node_sets; + node_sets[0] = std::vector{0, 2}; + node_sets[1] = std::vector{1, 3}; + + REQUIRE(mesh->create_node_sets(node_sets, true) == true); + + //! Constraints object + auto constraints = std::make_shared>(mesh); + + int set_id = 0; + double pressure = 500.2; + // Add pressure constraint to mesh + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, set_id, 0, pressure) == true); + + // Add pressure constraint to all nodes in mesh + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, -1, 0, pressure) == true); + } + // Test assign velocity constraints to nodes SECTION("Check assign velocity constraints to nodes") { // Vector of particle coordinates @@ -1192,6 +1216,24 @@ TEST_CASE("Mesh is checked for 2D case", "[mesh][2D]") { friction_constraints) == false); } + // Test assign pressure constraints to nodes + SECTION("Check assign pressure constraints to nodes") { + // Vector of pressure constraints + std::vector> pressure_constraints; + //! Constraints object + auto constraints = std::make_shared>(mesh); + // Constraint + pressure_constraints.emplace_back(std::make_tuple(0, 500.5)); + pressure_constraints.emplace_back(std::make_tuple(1, 210.5)); + pressure_constraints.emplace_back(std::make_tuple(2, 320.2)); + pressure_constraints.emplace_back(std::make_tuple(3, 0.0)); + + REQUIRE(constraints->assign_nodal_pressure_constraints( + 0, pressure_constraints) == true); + REQUIRE(constraints->assign_nodal_pressure_constraints( + 1, pressure_constraints) == false); + } + // Test assign nodes concentrated_forces SECTION("Check assign nodes concentrated_forces") { // Vector of node coordinates diff --git a/tests/mesh_test_3d.cc b/tests/mesh_test_3d.cc index 06f4e0cf2..5405bb5cc 100644 --- a/tests/mesh_test_3d.cc +++ b/tests/mesh_test_3d.cc @@ -198,11 +198,13 @@ TEST_CASE("Mesh is checked for 3D case", "[mesh][3D]") { REQUIRE(mesh->status() == true); // Check number of particles in mesh REQUIRE(mesh->nparticles() == 2); + REQUIRE(mesh->nparticles("P3D") == 2); // Remove particle 2 and check REQUIRE(mesh->remove_particle(particle2) == true); // Check number of particles in mesh REQUIRE(mesh->nparticles() == 1); + REQUIRE(mesh->nparticles("P3D") == 1); int mpi_size; MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); @@ -225,6 +227,7 @@ TEST_CASE("Mesh is checked for 3D case", "[mesh][3D]") { mesh->remove_all_nonrank_particles(); // Check number of particles in mesh REQUIRE(mesh->nparticles() == 0); + REQUIRE(mesh->nparticles("P3D") == 0); // Add and use remove all particles REQUIRE(mesh->add_particle(particle1) == true); @@ -1001,7 +1004,7 @@ TEST_CASE("Mesh is checked for 3D case", "[mesh][3D]") { } // Test HDF5 SECTION("Write particles HDF5") { - REQUIRE(mesh->write_particles_hdf5(0, "particles-3d.h5") == true); + REQUIRE(mesh->write_particles_hdf5("particles-3d.h5") == true); auto phdf5 = mesh->particles_hdf5(); REQUIRE(phdf5.size() == mesh->nparticles()); @@ -1216,6 +1219,27 @@ TEST_CASE("Mesh is checked for 3D case", "[mesh][3D]") { set_id, velocity_constraint) == false); } + SECTION("Check assign pressure constraints to nodes") { + tsl::robin_map> node_sets; + node_sets[0] = std::vector{0, 2}; + node_sets[1] = std::vector{1, 3}; + + REQUIRE(mesh->create_node_sets(node_sets, true) == true); + + //! Constraints object + auto constraints = std::make_shared>(mesh); + + int set_id = 0; + double pressure = 500.2; + // Add pressure constraint to mesh + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, set_id, 0, pressure) == true); + + // Add pressure constraint to all nodes in mesh + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, -1, 0, pressure) == true); + } + SECTION("Check assign friction constraints to nodes") { tsl::robin_map> node_sets; node_sets[0] = std::vector{0, 2}; @@ -1303,6 +1327,24 @@ TEST_CASE("Mesh is checked for 3D case", "[mesh][3D]") { friction_constraints) == false); } + // Test assign pressure constraints to nodes + SECTION("Check assign pressure constraints to nodes") { + // Vector of pressure constraints + std::vector> pressure_constraints; + //! Constraints object + auto constraints = std::make_shared>(mesh); + // Constraint + pressure_constraints.emplace_back(std::make_tuple(0, 500.5)); + pressure_constraints.emplace_back(std::make_tuple(1, 210.5)); + pressure_constraints.emplace_back(std::make_tuple(2, 320.2)); + pressure_constraints.emplace_back(std::make_tuple(3, 0.0)); + + REQUIRE(constraints->assign_nodal_pressure_constraints( + 0, pressure_constraints) == true); + REQUIRE(constraints->assign_nodal_pressure_constraints( + 1, pressure_constraints) == false); + } + // Test assign nodes concentrated_forces SECTION("Check assign nodes concentrated_forces") { // Vector of node coordinates diff --git a/tests/mpi_transfer_particle_test.cc b/tests/mpi_transfer_particle_test.cc index f4aa60fb5..1914cb720 100644 --- a/tests/mpi_transfer_particle_test.cc +++ b/tests/mpi_transfer_particle_test.cc @@ -6,12 +6,12 @@ #include "data_types.h" #include "element.h" #include "graph.h" -#include "hdf5_particle.h" #include "hexahedron_element.h" #include "material.h" #include "mesh.h" #include "node.h" #include "particle.h" +#include "pod_particle.h" #include "quadrilateral_element.h" #ifdef USE_MPI diff --git a/tests/nodal_properties_test.cc b/tests/nodes/nodal_properties_test.cc similarity index 100% rename from tests/nodal_properties_test.cc rename to tests/nodes/nodal_properties_test.cc diff --git a/tests/node_map_test.cc b/tests/nodes/node_map_test.cc similarity index 100% rename from tests/node_map_test.cc rename to tests/nodes/node_map_test.cc diff --git a/tests/node_test.cc b/tests/nodes/node_test.cc similarity index 97% rename from tests/node_test.cc rename to tests/nodes/node_test.cc index 3cc92d2a5..8b2d4968d 100644 --- a/tests/node_test.cc +++ b/tests/nodes/node_test.cc @@ -154,10 +154,20 @@ TEST_CASE("Node is checked for 1D case", "[node][1D]") { REQUIRE_NOTHROW(node->update_mass(false, Nphase, mass)); REQUIRE(node->mass(Nphase) == Approx(0.0).epsilon(Tolerance)); // Try to update pressure to 2000, should throw and keep to 1000. - pressure = 1000.; - const double pmass = 1.5; node->assign_pressure(Nphase, pressure); REQUIRE(node->pressure(Nphase) == Approx(1000.0).epsilon(Tolerance)); + // Check pressure constraints + SECTION("Check nodal pressure constraints") { + // Check assign pressure constraint + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NSolid, 8000, + nullptr) == true); + // Check apply pressure constraint + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NSolid)); + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(8000).epsilon(Tolerance)); + } } SECTION("Check external force") { @@ -621,10 +631,20 @@ TEST_CASE("Node is checked for 2D case", "[node][2D]") { REQUIRE_NOTHROW(node->update_mass(false, Nphase, mass)); REQUIRE(node->mass(Nphase) == Approx(0.0).epsilon(Tolerance)); // Try to update pressure to 2000, should throw and keep to 1000. - pressure = 1000.; - const double pmass = 1.5; node->assign_pressure(Nphase, pressure); REQUIRE(node->pressure(Nphase) == Approx(1000.0).epsilon(Tolerance)); + // Check pressure constraints + SECTION("Check nodal pressure constraints") { + // Check assign pressure constraint + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NSolid, 8000, + nullptr) == true); + // Check apply pressure constraint + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NSolid)); + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(8000).epsilon(Tolerance)); + } } SECTION("Check volume") { @@ -1225,10 +1245,20 @@ TEST_CASE("Node is checked for 3D case", "[node][3D]") { REQUIRE_NOTHROW(node->update_mass(false, Nphase, mass)); REQUIRE(node->mass(Nphase) == Approx(0.0).epsilon(Tolerance)); // Try to update pressure to 2000, should throw and keep to 1000. - pressure = 1000.; - const double pmass = 1.5; node->assign_pressure(Nphase, pressure); REQUIRE(node->pressure(Nphase) == Approx(1000.0).epsilon(Tolerance)); + // Check pressure constraints + SECTION("Check nodal pressure constraints") { + // Check assign pressure constraint + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NSolid, 8000, + nullptr) == true); + // Check apply pressure constraint + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NSolid)); + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(8000).epsilon(Tolerance)); + } } SECTION("Check external force") { diff --git a/tests/nodes/node_twophase_test.cc b/tests/nodes/node_twophase_test.cc new file mode 100644 index 000000000..3740313a4 --- /dev/null +++ b/tests/nodes/node_twophase_test.cc @@ -0,0 +1,2605 @@ +#include +#include +#include + +#include "Eigen/Dense" +#include "catch.hpp" + +#include "function_base.h" +#include "geometry.h" +#include "node.h" + +// Check node class for 1D case +TEST_CASE("Twophase Node is checked for 1D case", "[node][1D][2Phase]") { + const unsigned Dim = 1; + const unsigned Dof = 1; + const unsigned Nphases = 2; + Eigen::Matrix coords; + coords.setZero(); + + // Check for id = 0 + SECTION("Node id is zero") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == 0); + } + + // Check for id is a positive value + SECTION("Node id is positive") { + mpm::Index id = std::numeric_limits::max(); + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == std::numeric_limits::max()); + } + + // Check for degrees of freedom + SECTION("Check degrees of freedom") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->dof() == 1); + } + + // Check status + SECTION("Check status") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->status() == false); + node->assign_status(true); + REQUIRE(node->status() == true); + } + + SECTION("Boundary ghost id") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + node->ghost_id(5); + REQUIRE(node->ghost_id() == 5); + } + + // Check MPI Rank + SECTION("Check MPI Rank") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == 0); + + // Assign MPI ranks + node->mpi_rank(0); + node->mpi_rank(0); + node->mpi_rank(1); + + std::set ranks = node->mpi_ranks(); + REQUIRE(ranks.size() == 2); + std::vector mpi_ranks = {0, 1}; + unsigned i = 0; + for (auto it = ranks.begin(); it != ranks.end(); ++it, ++i) + REQUIRE(*it == mpi_ranks.at(i)); + } + + // Test coordinates function + SECTION("coordinates function is checked") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + + std::shared_ptr> node = + std::make_shared>(id, coords); + + // Check for coordinates being zero + auto coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + REQUIRE(coordinates.size() == Dim); + + // Check for negative value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = -1. * std::numeric_limits::max(); + node->assign_coordinates(coords); + coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + + // Check for positive value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = std::numeric_limits::max(); + node->assign_coordinates(coords); + coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + } + + SECTION("Check nodal properties") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> node = + std::make_shared>(id, coords); + + // Initialise two-phase node + REQUIRE_NOTHROW(node->initialise_twophase()); + + // Check mass + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + double solid_mass = 100.5; + double liquid_mass = 200.5; + // Update mass to 100.5 and 200.5 + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.5).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(200.5).epsilon(Tolerance)); + // Update mass to 201 and 401 + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(201.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(401.0).epsilon(Tolerance)); + // Assign mass to 100 and 200 + solid_mass = 100.; + liquid_mass = 200.; + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(200.0).epsilon(Tolerance)); + + SECTION("Check nodal pressure") { + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + double pressure = 1000.7; + double pore_pressure = 2000.7; + // Update pressure to 1000.7 and 2000.7 + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NSolid, + solid_mass * pressure)); + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NLiquid, + liquid_mass * pore_pressure)); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.7).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.7).epsilon(Tolerance)); + // Update pressure to 2001.4 and 4001.4 + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NSolid, + solid_mass * pressure)); + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NLiquid, + liquid_mass * pore_pressure)); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(2001.4).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(4001.4).epsilon(Tolerance)); + // Assign pressure to 1000 and 2000 + pressure = 1000.; + pore_pressure = 2000.; + node->assign_pressure(mpm::NodePhase::NSolid, pressure); + node->assign_pressure(mpm::NodePhase::NLiquid, pore_pressure); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.0).epsilon(Tolerance)); + // Assign mass to 0 + solid_mass = 0.; + liquid_mass = 0.; + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + // Try to update pressure to 2000, should throw and keep to 1000. + node->assign_pressure(mpm::NodePhase::NSolid, pressure); + node->assign_pressure(mpm::NodePhase::NLiquid, pore_pressure); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.0).epsilon(Tolerance)); + // Check pressure constraints + SECTION("Check nodal pressure constraints") { + // Check assign pressure constraint + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NSolid, 8000, + nullptr) == true); + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NLiquid, 7000, + nullptr) == true); + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NGas, 7000, + nullptr) == false); + // Check apply pressure constraint + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NSolid)); + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NLiquid)); + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(8000).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(7000).epsilon(Tolerance)); + } + } + + SECTION("Check external force") { + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10.; + + // Check current external force is zero + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_external_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_external_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_external_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + SECTION("Check concentrated force") { + // Set external force to zero + force.setZero(); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NMixture, force)); + + // concentrated force + std::shared_ptr ffunction = nullptr; + double concentrated_force = 65.32; + const unsigned Direction = 0; + // Check external force + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + + REQUIRE(node->assign_concentrated_force(mpm::NodePhase::NMixture, + Direction, concentrated_force, + ffunction) == true); + + double current_time = 0.0; + node->apply_concentrated_force(mpm::NodePhase::NMixture, current_time); + + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(concentrated_force).epsilon(Tolerance)); + else + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check for incorrect direction / phase + const unsigned wrong_dir = 4; + REQUIRE(node->assign_concentrated_force(mpm::NodePhase::NMixture, + wrong_dir, concentrated_force, + ffunction) == false); + + // Check again to ensure value hasn't been updated + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(concentrated_force).epsilon(Tolerance)); + else + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + } + } + } + + SECTION("Check internal force") { + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10.; + + // Check current internal force is zero + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_internal_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + } + + SECTION("Check drag force coefficient") { + // Create a force vector + Eigen::Matrix drag_force_coefficient; + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + drag_force_coefficient(i) = 10.; + + // Check current drag force coefficient is zero + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(0.).epsilon(Tolerance)); + + // Update drag force coefficient to 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(true, drag_force_coefficient)); + + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(10.).epsilon(Tolerance)); + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(true, drag_force_coefficient)); + + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(20.).epsilon(Tolerance)); + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(false, drag_force_coefficient)); + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(10.).epsilon(Tolerance)); + } + + SECTION("Check compute acceleration and velocity") { + // Time step + const double dt = 0.1; + + // Nodal mass + double solid_mass = 100.; + double liquid_mass = 100.; + // Update mass to 100.5 + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(solid_mass).epsilon(Tolerance)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(liquid_mass).epsilon(Tolerance)); + + // Check internal force + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + // Internal force + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(force(i)).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.5 * force(i)).epsilon(Tolerance)); + } + + // External force + for (unsigned i = 0; i < force.size(); ++i) force(i) = 5. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_external_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(force(i)).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.5 * force(i)).epsilon(Tolerance)); + } + + // Drag force + Eigen::Matrix drag_force_coefficient; + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + drag_force_coefficient(i) = 5. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(false, drag_force_coefficient)); + for (unsigned i = 0; i < force.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(drag_force_coefficient(i)).epsilon(Tolerance)); + + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + true); + + // Check acceleration + Eigen::Matrix liquid_acceleration; + liquid_acceleration << 0.; + Eigen::Matrix solid_acceleration; + solid_acceleration << 0.; + + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Check velocity + Eigen::Matrix solid_velocity = solid_acceleration * dt; + Eigen::Matrix liquid_velocity = liquid_acceleration * dt; + for (unsigned i = 0; i < solid_velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(solid_velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + } + + // Apply friction constraints + REQUIRE(node->assign_friction_constraint(0, 1., 0.5) == true); + // Apply friction constraints + REQUIRE(node->assign_friction_constraint(-1, 1., 0.5) == false); + REQUIRE(node->assign_friction_constraint(3, 1., 0.5) == false); + + // Test acceleration with constraints + solid_acceleration[0] = 0.5 * solid_acceleration[0]; + liquid_acceleration[0] = 0.5 * liquid_acceleration[0]; + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Apply cundall damping when calculating acceleration + REQUIRE(node->compute_acceleration_velocity_twophase_explicit_cundall( + dt, 0.05) == true); + + // Test acceleration with cundall damping + solid_acceleration[0] = 0.; + liquid_acceleration[0] = 0.; + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, 10.5) == true); + REQUIRE(node->assign_velocity_constraint(1, 10.5) == true); + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + true); + + // Test velocity with constraints + solid_velocity[0] = 10.5; + liquid_velocity[0] = 10.5; + for (unsigned i = 0; i < solid_velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(solid_velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + } + + // Test acceleration with constraints + solid_acceleration[0] = 0.; + liquid_acceleration[0] = 0.; + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Exception check when mass is zero + // Update mass to 0. + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NSolid, 0.)); + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NLiquid, 0.)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + false); + } + + SECTION("Check momentum and velocity") { + // Check momentum + Eigen::Matrix momentum; + for (unsigned i = 0; i < momentum.size(); ++i) momentum(i) = 10.; + + // Check initial momentum + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check update momentum to 10 + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Check update momentum to 20 + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(20.).epsilon(Tolerance)); + } + + // Check assign momentum to 10 + REQUIRE_NOTHROW( + node->update_momentum(false, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(false, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Check zero velocity + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check mass + double mass = 0.; + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NSolid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NLiquid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + // Compute and check velocity this should throw zero mass + node->compute_velocity(); + + mass = 100.; + // Update mass to 100. + REQUIRE_NOTHROW(node->update_mass(true, mpm::NodePhase::NSolid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.).epsilon(Tolerance)); + REQUIRE_NOTHROW(node->update_mass(true, mpm::NodePhase::NLiquid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(100.).epsilon(Tolerance)); + + // Compute and check velocity + node->compute_velocity(); + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(0.1).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(0.1).epsilon(Tolerance)); + } + + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, 10.5) == true); + // Check out of bounds condition + REQUIRE(node->assign_velocity_constraint(1, 10.5) == true); + + // Check velocity before constraints + Eigen::Matrix velocity; + velocity << 0.1; + for (unsigned i = 0; i < velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + // Apply constraints + node->apply_velocity_constraints(); + + // Check apply constraints + velocity << 10.5; + for (unsigned i = 0; i < velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + } + + SECTION("Check acceleration") { + // Check acceleration + Eigen::Matrix acceleration; + for (unsigned i = 0; i < acceleration.size(); ++i) acceleration(i) = 5.; + + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + REQUIRE_NOTHROW(node->update_acceleration(true, mpm::NodePhase::NSolid, + acceleration)); + REQUIRE_NOTHROW(node->update_acceleration(true, mpm::NodePhase::NLiquid, + 0.5 * acceleration)); + + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(5.).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(2.5).epsilon(Tolerance)); + } + + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, 10.5) == true); + // Check out of bounds condition + REQUIRE(node->assign_velocity_constraint(1, 12.5) == true); + + // Check acceleration before constraints + acceleration.resize(Dim); + acceleration << 5.; + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(0.5 * acceleration(i)).epsilon(Tolerance)); + } + + // Apply constraints + node->apply_velocity_constraints(); + + // Check apply constraints + acceleration << 0.0; + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + } + + SECTION("Check node material ids") { + // Add material to nodes + node->append_material_id(0); + node->append_material_id(1); + node->append_material_id(4); + node->append_material_id(0); + node->append_material_id(2); + + // Check size of material_ids + REQUIRE(node->material_ids().size() == 4); + + // Check elements of material_ids + std::vector material_ids = {0, 1, 2, 4}; + auto mat_ids = node->material_ids(); + unsigned i = 0; + for (auto mitr = mat_ids.begin(); mitr != mat_ids.end(); ++mitr, ++i) + REQUIRE(*mitr == material_ids.at(i)); + } + } +} + +// \brief Check node class for 2D case +TEST_CASE("Twophase Node is checked for 2D case", "[node][2D][2Phase]") { + const unsigned Dim = 2; + const unsigned Dof = 2; + const unsigned Nphases = 2; + + Eigen::Vector2d coords; + coords.setZero(); + + // Check for id = 0 + SECTION("Node id is zero") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == 0); + } + + // Check for id is a positive value + SECTION("Node id is positive") { + mpm::Index id = std::numeric_limits::max(); + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == std::numeric_limits::max()); + } + + // Check for degrees of freedom + SECTION("Check degrees of freedom") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->dof() == 2); + } + + // Check status + SECTION("Check status") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->status() == false); + node->assign_status(true); + REQUIRE(node->status() == true); + } + + SECTION("Boundary ghost id") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + node->ghost_id(5); + REQUIRE(node->ghost_id() == 5); + } + + // Check MPI Rank + SECTION("Check MPI Rank") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == 0); + + // Assign MPI ranks + node->mpi_rank(0); + node->mpi_rank(0); + node->mpi_rank(1); + + std::set ranks = node->mpi_ranks(); + REQUIRE(ranks.size() == 2); + std::vector mpi_ranks = {0, 1}; + unsigned i = 0; + for (auto it = ranks.begin(); it != ranks.end(); ++it, ++i) + REQUIRE(*it == mpi_ranks.at(i)); + } + + // Test coordinates function + SECTION("coordinates function is checked") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + + std::shared_ptr> node = + std::make_shared>(id, coords); + + // Check for coordinates being zero + auto coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + REQUIRE(coordinates.size() == Dim); + + // Check for negative value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = -1. * std::numeric_limits::max(); + node->assign_coordinates(coords); + coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + + // Check for positive value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = std::numeric_limits::max(); + node->assign_coordinates(coords); + coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + } + + SECTION("Check nodal properties") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> node = + std::make_shared>(id, coords); + + // Initialise two-phase node + REQUIRE_NOTHROW(node->initialise_twophase()); + + // Check mass + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + double solid_mass = 100.5; + double liquid_mass = 200.5; + // Update mass to 100.5 and 200.5 + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.5).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(200.5).epsilon(Tolerance)); + // Update mass to 201 and 401 + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(201.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(401.0).epsilon(Tolerance)); + // Assign mass to 100 and 200 + solid_mass = 100.; + liquid_mass = 200.; + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(200.0).epsilon(Tolerance)); + + SECTION("Check nodal pressure") { + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + double pressure = 1000.7; + double pore_pressure = 2000.7; + // Update pressure to 1000.7 and 2000.7 + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NSolid, + solid_mass * pressure)); + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NLiquid, + liquid_mass * pore_pressure)); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.7).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.7).epsilon(Tolerance)); + // Update pressure to 2001.4 and 4001.4 + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NSolid, + solid_mass * pressure)); + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NLiquid, + liquid_mass * pore_pressure)); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(2001.4).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(4001.4).epsilon(Tolerance)); + // Assign pressure to 1000 and 2000 + pressure = 1000.; + pore_pressure = 2000.; + node->assign_pressure(mpm::NodePhase::NSolid, pressure); + node->assign_pressure(mpm::NodePhase::NLiquid, pore_pressure); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.0).epsilon(Tolerance)); + // Assign mass to 0 + solid_mass = 0.; + liquid_mass = 0.; + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + // Try to update pressure to 2000, should throw and keep to 1000. + node->assign_pressure(mpm::NodePhase::NSolid, pressure); + node->assign_pressure(mpm::NodePhase::NLiquid, pore_pressure); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.0).epsilon(Tolerance)); + // Check pressure constraints + SECTION("Check nodal pressure constraints") { + // Check assign pressure constraint + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NSolid, 8000, + nullptr) == true); + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NLiquid, 7000, + nullptr) == true); + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NGas, 7000, + nullptr) == false); + // Check apply pressure constraint + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NSolid)); + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NLiquid)); + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(8000).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(7000).epsilon(Tolerance)); + } + } + + SECTION("Check volume") { + // Check volume + REQUIRE(node->volume(mpm::NodePhase::NMixture) == + Approx(0.0).epsilon(Tolerance)); + double volume = 100.5; + // Update volume to 100.5 + REQUIRE_NOTHROW( + node->update_volume(true, mpm::NodePhase::NMixture, volume)); + REQUIRE(node->volume(mpm::NodePhase::NMixture) == + Approx(100.5).epsilon(Tolerance)); + // Update volume to 201 + REQUIRE_NOTHROW( + node->update_volume(true, mpm::NodePhase::NMixture, volume)); + REQUIRE(node->volume(mpm::NodePhase::NMixture) == + Approx(201.0).epsilon(Tolerance)); + // Assign volume to 100 + volume = 100.; + REQUIRE_NOTHROW( + node->update_volume(false, mpm::NodePhase::NMixture, volume)); + REQUIRE(node->volume(mpm::NodePhase::NMixture) == + Approx(100.0).epsilon(Tolerance)); + } + + SECTION("Check external force") { + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10.; + + // Check current external force is zero + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_external_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_external_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_external_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + SECTION("Check concentrated force") { + // Set external force to zero + force.setZero(); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NMixture, force)); + + // Concentrated force + std::shared_ptr ffunction = nullptr; + double concentrated_force = 65.32; + const unsigned Direction = 0; + // Check traction + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + + REQUIRE(node->assign_concentrated_force(mpm::NodePhase::NMixture, + Direction, concentrated_force, + ffunction) == true); + double current_time = 0.0; + node->apply_concentrated_force(mpm::NodePhase::NMixture, current_time); + + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(concentrated_force).epsilon(Tolerance)); + + else + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check for incorrect direction / phase + const unsigned wrong_dir = 4; + REQUIRE(node->assign_concentrated_force(mpm::NodePhase::NMixture, + wrong_dir, concentrated_force, + ffunction) == false); + + // Check again to ensure value hasn't been updated + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(concentrated_force).epsilon(Tolerance)); + else + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + } + } + } + + SECTION("Check internal force") { + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10.; + + // Check current internal force is zero + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_internal_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + } + + SECTION("Check drag force coefficient") { + // Create a force vector + Eigen::Matrix drag_force_coefficient; + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + drag_force_coefficient(i) = 10.; + + // Check current drag force coefficient is zero + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(0.).epsilon(Tolerance)); + + // Update drag force coefficient to 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(true, drag_force_coefficient)); + + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(10.).epsilon(Tolerance)); + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(true, drag_force_coefficient)); + + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(20.).epsilon(Tolerance)); + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(false, drag_force_coefficient)); + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(10.).epsilon(Tolerance)); + } + + SECTION("Check compute acceleration and velocity") { + // Time step + const double dt = 0.1; + + // Nodal mass + double solid_mass = 100.; + double liquid_mass = 100.; + // Update mass to 100. + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(solid_mass).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(liquid_mass).epsilon(Tolerance)); + + // Check internal force + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + // Internal force + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(force(i)).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.5 * force(i)).epsilon(Tolerance)); + } + + // External force + for (unsigned i = 0; i < force.size(); ++i) force(i) = 5. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_external_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(force(i)).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.5 * force(i)).epsilon(Tolerance)); + } + + // Drag force + Eigen::Matrix drag_force_coefficient; + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + drag_force_coefficient(i) = 5. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(false, drag_force_coefficient)); + for (unsigned i = 0; i < force.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(drag_force_coefficient(i)).epsilon(Tolerance)); + + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + true); + + // Check acceleration + Eigen::Matrix liquid_acceleration; + liquid_acceleration << 0., 0.075; + Eigen::Matrix solid_acceleration; + solid_acceleration << 0., 0.075; + + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Check velocity + Eigen::Matrix solid_velocity = solid_acceleration * dt; + Eigen::Matrix liquid_velocity = liquid_acceleration * dt; + for (unsigned i = 0; i < solid_velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(solid_velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + } + + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, 10.5) == true); + REQUIRE(node->assign_velocity_constraint(1, 0.03) == true); + REQUIRE(node->assign_velocity_constraint(2, 20.5) == true); + REQUIRE(node->assign_velocity_constraint(3, 1.03) == true); + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + true); + + // Test velocity with constraints + solid_velocity << 10.5, 0.03; + liquid_velocity << 20.5, 1.03; + for (unsigned i = 0; i < solid_velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(solid_velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + } + + // Test acceleration with constraints + solid_acceleration.setZero(); + liquid_acceleration.setZero(); + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Apply cundall damping when calculating acceleration + REQUIRE(node->compute_acceleration_velocity_twophase_explicit_cundall( + dt, 0.05) == true); + + // Test acceleration with cundall damping + solid_acceleration << 0., 0.; + liquid_acceleration << 0., 0.; + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Exception check when mass is zero + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NSolid, 0.)); + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NLiquid, 0.)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + false); + } + + SECTION("Check momentum, velocity and acceleration") { + // Time step + const double dt = 0.1; + + // Check momentum + Eigen::Matrix momentum; + for (unsigned i = 0; i < momentum.size(); ++i) momentum(i) = 10.; + + // Check initial momentum + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check update momentum to 10 + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Check update momentum to 20 + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(20.).epsilon(Tolerance)); + } + + // Check assign momentum to 10 + REQUIRE_NOTHROW( + node->update_momentum(false, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(false, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Check zero velocity + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check mass + double mass = 0.; + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NSolid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NLiquid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + // Compute and check velocity this should throw zero mass + node->compute_velocity(); + + mass = 100.; + // Update mass to 100.5 + REQUIRE_NOTHROW(node->update_mass(true, mpm::NodePhase::NSolid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.).epsilon(Tolerance)); + REQUIRE_NOTHROW(node->update_mass(true, mpm::NodePhase::NLiquid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(100.).epsilon(Tolerance)); + + // Compute and check velocity + node->compute_velocity(); + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(0.1).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(0.1).epsilon(Tolerance)); + } + + // Check acceleration + Eigen::Matrix acceleration; + for (unsigned i = 0; i < acceleration.size(); ++i) acceleration(i) = 5.; + + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + REQUIRE_NOTHROW(node->update_acceleration(true, mpm::NodePhase::NSolid, + acceleration)); + REQUIRE_NOTHROW(node->update_acceleration(true, mpm::NodePhase::NLiquid, + acceleration)); + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(5.).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + // Check if exception is handled + Eigen::Matrix acceleration_bad; + for (unsigned i = 0; i < acceleration_bad.size(); ++i) + acceleration_bad(i) = 10.; + + // Check velocity before constraints + Eigen::Matrix velocity; + velocity << 0.1, 0.1; + for (unsigned i = 0; i < velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + // Check acceleration before constraints + acceleration << 5., 5.; + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + + SECTION("Check Cartesian velocity constraints") { + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, -12.5) == true); + // Check out of bounds condition + REQUIRE(node->assign_velocity_constraint(2, -12.5) == true); + + // Apply constraints + node->apply_velocity_constraints(); + + // Check apply constraints + velocity << -12.5, 0.1; + for (unsigned i = 0; i < velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + acceleration << 0., 5.; + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + } + + SECTION("Check general velocity constraints in 1 direction") { + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, -12.5) == true); + REQUIRE(node->assign_velocity_constraint(2, -12.5) == true); + + // Apply rotation matrix with Euler angles alpha = 10 deg, beta + // = 30 deg + Eigen::Matrix euler_angles; + euler_angles << 10. * M_PI / 180, 30. * M_PI / 180; + const auto rotation_matrix = + mpm::geometry::rotation_matrix(euler_angles); + node->assign_rotation_matrix(rotation_matrix); + const auto inverse_rotation_matrix = rotation_matrix.inverse(); + + // Apply inclined velocity constraints + node->apply_velocity_constraints(); + + // Check applied velocity constraints in the global coordinates + velocity << -9.583478335521184, -8.025403099849004; + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + // Check that the velocity is as specified in local coordinate + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NSolid))(0) == + Approx(-12.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NLiquid))(0) == + Approx(-12.5).epsilon(Tolerance)); + + // Check applied constraints on acceleration in the global coordinates + acceleration << -0.396139826697847, 0.472101061636807; + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + + // Check that the acceleration is 0 in local coordinate + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(0) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NLiquid))(0) == + Approx(0).epsilon(Tolerance)); + } + + SECTION("Check general velocity constraints in all directions") { + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, -12.5) == true); + REQUIRE(node->assign_velocity_constraint(1, 7.5) == true); + REQUIRE(node->assign_velocity_constraint(2, -12.5) == true); + REQUIRE(node->assign_velocity_constraint(3, 7.5) == true); + + // Apply rotation matrix with Euler angles alpha = -10 deg, beta = 30 + // deg + Eigen::Matrix euler_angles; + euler_angles << -10. * M_PI / 180, 30. * M_PI / 180; + const auto rotation_matrix = + mpm::geometry::rotation_matrix(euler_angles); + node->assign_rotation_matrix(rotation_matrix); + const auto inverse_rotation_matrix = rotation_matrix.inverse(); + + // Apply inclined velocity constraints + node->apply_velocity_constraints(); + + // Check applied velocity constraints in the global coordinates + velocity << -14.311308834766370, 2.772442864323454; + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + // Check that the velocity is as specified in local coordinate + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NSolid))(0) == + Approx(-12.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NSolid))(1) == + Approx(7.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NLiquid))(0) == + Approx(-12.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NLiquid))(1) == + Approx(7.5).epsilon(Tolerance)); + + // Check applied constraints on acceleration in the global coordinates + acceleration << 0, 0; + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + + // Check that the acceleration is 0 in local coordinate + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(0) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(1) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NLiquid))(0) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NLiquid))(1) == + Approx(0).epsilon(Tolerance)); + } + + SECTION("Check Cartesian friction constraints") { + // Apply friction constraints + REQUIRE(node->assign_friction_constraint(1, 1, 0.2) == true); + // Check out of bounds condition + REQUIRE(node->assign_friction_constraint(2, 1, 0.2) == false); + + // Apply friction constraints + node->apply_friction_constraints(dt); + + // Check apply constraints + acceleration << 4., 5.; + for (unsigned i = 0; i < acceleration.size(); ++i) + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + + SECTION("Check general friction constraints in 1 direction") { + // Apply friction constraints + REQUIRE(node->assign_friction_constraint(1, 1, 0.2) == true); + + // Apply rotation matrix with Euler angles alpha = 10 deg, beta = 30 deg + Eigen::Matrix euler_angles; + euler_angles << 10. * M_PI / 180, 30. * M_PI / 180; + const auto rotation_matrix = + mpm::geometry::rotation_matrix(euler_angles); + node->assign_rotation_matrix(rotation_matrix); + const auto inverse_rotation_matrix = rotation_matrix.inverse(); + + // Apply general friction constraints + node->apply_friction_constraints(dt); + + // Check applied constraints on acceleration in the global coordinates + acceleration << 4.905579787672637, 4.920772034660430; + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + + // Check the acceleration in local coordinate + acceleration << 6.920903430595146, 0.616284167162194; + for (unsigned i = 0; i < Dim; ++i) + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + } + + SECTION("Check node material ids") { + // Add material to nodes + node->append_material_id(0); + node->append_material_id(1); + node->append_material_id(4); + node->append_material_id(0); + node->append_material_id(2); + + // Check size of material_ids + REQUIRE(node->material_ids().size() == 4); + + // Check elements of material_ids + std::vector material_ids = {0, 1, 2, 4}; + auto mat_ids = node->material_ids(); + unsigned i = 0; + for (auto mitr = mat_ids.begin(); mitr != mat_ids.end(); ++mitr, ++i) + REQUIRE(*mitr == material_ids.at(i)); + } + } +} + +// \brief Check node class for 3D case +TEST_CASE("Twophase Node is checked for 3D case", "[node][3D][2Phase]") { + const unsigned Dim = 3; + const unsigned Dof = 3; + const unsigned Nphases = 2; + + Eigen::Vector3d coords; + coords.setZero(); + + // Check for id = 0 + SECTION("Node id is zero") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == 0); + } + + // Check for id is a positive value + SECTION("Node id is positive") { + mpm::Index id = std::numeric_limits::max(); + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == std::numeric_limits::max()); + } + + // Check for degrees of freedom + SECTION("Check degrees of freedom") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->dof() == 3); + } + + // Check status + SECTION("Check status") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->status() == false); + node->assign_status(true); + REQUIRE(node->status() == true); + } + + SECTION("Boundary ghost id") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + node->ghost_id(5); + REQUIRE(node->ghost_id() == 5); + } + + // Check MPI Rank + SECTION("Check MPI Rank") { + mpm::Index id = 0; + std::shared_ptr> node = + std::make_shared>(id, coords); + REQUIRE(node->id() == 0); + + // Assign MPI ranks + node->mpi_rank(0); + node->mpi_rank(0); + node->mpi_rank(1); + + std::set ranks = node->mpi_ranks(); + REQUIRE(ranks.size() == 2); + std::vector mpi_ranks = {0, 1}; + unsigned i = 0; + for (auto it = ranks.begin(); it != ranks.end(); ++it, ++i) + REQUIRE(*it == mpi_ranks.at(i)); + } + + // Test coordinates function + SECTION("coordinates function is checked") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + + std::shared_ptr> node = + std::make_shared>(id, coords); + + // Check for coordinates being zero + auto coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + REQUIRE(coordinates.size() == Dim); + + // Check for negative value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = -1. * std::numeric_limits::max(); + node->assign_coordinates(coords); + coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + + // Check for positive value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = std::numeric_limits::max(); + node->assign_coordinates(coords); + coordinates = node->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + } + + SECTION("Check nodal properties") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> node = + std::make_shared>(id, coords); + + // Initialise two-phase node + REQUIRE_NOTHROW(node->initialise_twophase()); + + // Check mass + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + double solid_mass = 100.5; + double liquid_mass = 200.5; + // Update mass to 100.5 and 200.5 + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.5).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(200.5).epsilon(Tolerance)); + // Update mass to 201 and 401 + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(true, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(201.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(401.0).epsilon(Tolerance)); + // Assign mass to 100 and 200 + solid_mass = 100.; + liquid_mass = 200.; + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(200.0).epsilon(Tolerance)); + + SECTION("Check nodal pressure") { + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + double pressure = 1000.7; + double pore_pressure = 2000.7; + // Update pressure to 1000.7 and 2000.7 + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NSolid, + solid_mass * pressure)); + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NLiquid, + liquid_mass * pore_pressure)); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.7).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.7).epsilon(Tolerance)); + // Update pressure to 2001.4 and 4001.4 + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NSolid, + solid_mass * pressure)); + REQUIRE_NOTHROW(node->update_mass_pressure(mpm::NodePhase::NLiquid, + liquid_mass * pore_pressure)); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(2001.4).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(4001.4).epsilon(Tolerance)); + // Assign pressure to 1000 and 2000 + pressure = 1000.; + pore_pressure = 2000.; + node->assign_pressure(mpm::NodePhase::NSolid, pressure); + node->assign_pressure(mpm::NodePhase::NLiquid, pore_pressure); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.0).epsilon(Tolerance)); + // Assign mass to 0 + solid_mass = 0.; + liquid_mass = 0.; + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + // Try to update pressure to 2000, should throw and keep to 1000. + node->assign_pressure(mpm::NodePhase::NSolid, pressure); + node->assign_pressure(mpm::NodePhase::NLiquid, pore_pressure); + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(1000.0).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(2000.0).epsilon(Tolerance)); + // Check pressure constraints + SECTION("Check nodal pressure constraints") { + // Check assign pressure constraint + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NSolid, 8000, + nullptr) == true); + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NLiquid, 7000, + nullptr) == true); + REQUIRE(node->assign_pressure_constraint(mpm::NodePhase::NGas, 7000, + nullptr) == false); + // Check apply pressure constraint + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NSolid)); + REQUIRE_NOTHROW( + node->apply_pressure_constraint(mpm::NodePhase::NLiquid)); + // Check pressure + REQUIRE(node->pressure(mpm::NodePhase::NSolid) == + Approx(8000).epsilon(Tolerance)); + REQUIRE(node->pressure(mpm::NodePhase::NLiquid) == + Approx(7000).epsilon(Tolerance)); + } + } + + SECTION("Check external force") { + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10.; + + // Check current external force is zero + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_external_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_external_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_external_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + SECTION("Check concentrated force") { + // Set external force to zero + force.setZero(); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NMixture, force)); + + // Concentrated force + std::shared_ptr ffunction = nullptr; + double concentrated_force = 65.32; + const unsigned Direction = 0; + // Check traction + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + + REQUIRE(node->assign_concentrated_force(mpm::NodePhase::NMixture, + Direction, concentrated_force, + ffunction) == true); + double current_time = 0.0; + node->apply_concentrated_force(mpm::NodePhase::NMixture, current_time); + + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(concentrated_force).epsilon(Tolerance)); + + else + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check for incorrect direction / phase + const unsigned wrong_dir = 4; + REQUIRE(node->assign_concentrated_force(mpm::NodePhase::NMixture, + wrong_dir, concentrated_force, + ffunction) == false); + + // Check again to ensure value hasn't been updated + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(concentrated_force).epsilon(Tolerance)); + else + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + } + } + } + + SECTION("Check internal force") { + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10.; + + // Check current internal force is zero + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_internal_force(true, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force(true, mpm::NodePhase::NLiquid, + 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + } + + SECTION("Check drag force coefficient") { + // Create a force vector + Eigen::Matrix drag_force_coefficient; + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + drag_force_coefficient(i) = 10.; + + // Check current drag force coefficient is zero + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(0.).epsilon(Tolerance)); + + // Update drag force coefficient to 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(true, drag_force_coefficient)); + + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(10.).epsilon(Tolerance)); + + // Update force to 20.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(true, drag_force_coefficient)); + + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(20.).epsilon(Tolerance)); + + // Assign force as 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(false, drag_force_coefficient)); + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(10.).epsilon(Tolerance)); + } + + SECTION("Check compute acceleration and velocity") { + // Time step + const double dt = 0.1; + + // Nodal mass + double solid_mass = 100.; + double liquid_mass = 100.; + // Update mass to 100. + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NSolid, solid_mass)); + REQUIRE_NOTHROW( + node->update_mass(false, mpm::NodePhase::NLiquid, liquid_mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(solid_mass).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(liquid_mass).epsilon(Tolerance)); + + // Check internal force + // Create a force vector + Eigen::Matrix force; + for (unsigned i = 0; i < force.size(); ++i) force(i) = 10. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_internal_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_internal_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + // Internal force + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->internal_force(mpm::NodePhase::NMixture)(i) == + Approx(force(i)).epsilon(Tolerance)); + REQUIRE(node->internal_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.5 * force(i)).epsilon(Tolerance)); + } + + // External force + for (unsigned i = 0; i < force.size(); ++i) force(i) = 5. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_external_force(false, mpm::NodePhase::NMixture, force)); + REQUIRE_NOTHROW(node->update_external_force( + false, mpm::NodePhase::NLiquid, 0.5 * force)); + for (unsigned i = 0; i < force.size(); ++i) { + REQUIRE(node->external_force(mpm::NodePhase::NMixture)(i) == + Approx(force(i)).epsilon(Tolerance)); + REQUIRE(node->external_force(mpm::NodePhase::NLiquid)(i) == + Approx(0.5 * force(i)).epsilon(Tolerance)); + } + + // Drag force + Eigen::Matrix drag_force_coefficient; + for (unsigned i = 0; i < drag_force_coefficient.size(); ++i) + drag_force_coefficient(i) = 5. * i; + // Update force to 10.0 + REQUIRE_NOTHROW( + node->update_drag_force_coefficient(false, drag_force_coefficient)); + for (unsigned i = 0; i < force.size(); ++i) + REQUIRE(node->drag_force_coefficient()(i) == + Approx(drag_force_coefficient(i)).epsilon(Tolerance)); + + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + true); + + // Check acceleration + Eigen::Matrix solid_acceleration; + solid_acceleration << 0., 0.075, 0.15; + Eigen::Matrix liquid_acceleration; + liquid_acceleration << 0., 0.075, 0.15; + + // Check acceleration + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Check velocity + Eigen::Matrix solid_velocity = solid_acceleration * dt; + Eigen::Matrix liquid_velocity = liquid_acceleration * dt; + for (unsigned i = 0; i < solid_velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(solid_velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + } + + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, 10.5) == true); + REQUIRE(node->assign_velocity_constraint(1, 0.03) == true); + REQUIRE(node->assign_velocity_constraint(2, 5.13) == true); + REQUIRE(node->assign_velocity_constraint(3, 20.5) == true); + REQUIRE(node->assign_velocity_constraint(4, 1.03) == true); + REQUIRE(node->assign_velocity_constraint(5, 7.13) == true); + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + true); + + // Test velocity with constraints + solid_velocity << 10.5, 0.03, 5.13; + liquid_velocity << 20.5, 1.03, 7.13; + for (unsigned i = 0; i < solid_velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(solid_velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + } + + // Test acceleration with constraints + solid_acceleration.setZero(); + liquid_acceleration.setZero(); + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Apply cundall damping when calculating acceleration + REQUIRE(node->compute_acceleration_velocity_twophase_explicit_cundall( + dt, 0.05) == true); + + // Test acceleration with cundall damping + solid_acceleration << 0., 0., 0.; + liquid_acceleration << 0., 0., 0.; + for (unsigned i = 0; i < solid_acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(solid_acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(liquid_acceleration(i)).epsilon(Tolerance)); + } + + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, 10.5) == true); + REQUIRE(node->assign_velocity_constraint(1, 20.5) == true); + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + true); + + // Exception check when mass is zero + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NSolid, 0.)); + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NLiquid, 0.)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->compute_acceleration_velocity_twophase_explicit(dt) == + false); + } + + SECTION("Check momentum, velocity and acceleration") { + // Time step + const double dt = 0.1; + + // Check momentum + Eigen::Matrix momentum; + for (unsigned i = 0; i < momentum.size(); ++i) momentum(i) = 10.; + + // Check initial momentum + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check update momentum to 10 + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Check update momentum to 20 + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(true, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(20.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(20.).epsilon(Tolerance)); + } + + // Check assign momentum to 10 + REQUIRE_NOTHROW( + node->update_momentum(false, mpm::NodePhase::NSolid, momentum)); + REQUIRE_NOTHROW( + node->update_momentum(false, mpm::NodePhase::NLiquid, momentum)); + for (unsigned i = 0; i < momentum.size(); ++i) { + REQUIRE(node->momentum(mpm::NodePhase::NSolid)(i) == + Approx(10.).epsilon(Tolerance)); + REQUIRE(node->momentum(mpm::NodePhase::NLiquid)(i) == + Approx(10.).epsilon(Tolerance)); + } + + // Check zero velocity + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Check mass + double mass = 0.; + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NSolid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(0.0).epsilon(Tolerance)); + REQUIRE_NOTHROW(node->update_mass(false, mpm::NodePhase::NLiquid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(0.0).epsilon(Tolerance)); + // Compute and check velocity this should throw zero mass + node->compute_velocity(); + + mass = 100.; + // Update mass to 100.5 + REQUIRE_NOTHROW(node->update_mass(true, mpm::NodePhase::NSolid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NSolid) == + Approx(100.).epsilon(Tolerance)); + REQUIRE_NOTHROW(node->update_mass(true, mpm::NodePhase::NLiquid, mass)); + REQUIRE(node->mass(mpm::NodePhase::NLiquid) == + Approx(100.).epsilon(Tolerance)); + + // Check zero velocity + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + // Compute and check velocity + node->compute_velocity(); + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(0.1).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(0.1).epsilon(Tolerance)); + } + + // Check acceleration + Eigen::Matrix acceleration; + for (unsigned i = 0; i < acceleration.size(); ++i) acceleration(i) = 5.; + + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(0.).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(0.).epsilon(Tolerance)); + } + + REQUIRE_NOTHROW(node->update_acceleration(true, mpm::NodePhase::NSolid, + acceleration)); + REQUIRE_NOTHROW(node->update_acceleration(true, mpm::NodePhase::NLiquid, + acceleration)); + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(5.).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(5.).epsilon(Tolerance)); + } + + // Check velocity before constraints + Eigen::Matrix velocity; + velocity << 0.1, 0.1, 0.1; + for (unsigned i = 0; i < velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + // Check acceleration before constraints + acceleration << 5., 5., 5.; + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + SECTION("Check Cartesian velocity constraints") { + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, -12.5) == true); + // Check out of bounds condition + REQUIRE(node->assign_velocity_constraint(3, -12.5) == true); + + // Apply constraints + node->apply_velocity_constraints(); + + // Check apply constraints + velocity << -12.5, 0.1, 0.1; + for (unsigned i = 0; i < velocity.size(); ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + acceleration << 0., 5., 5.; + for (unsigned i = 0; i < acceleration.size(); ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + } + SECTION("Check general velocity constraints in 2 directions") { + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, -12.5) == true); + REQUIRE(node->assign_velocity_constraint(1, 7.5) == true); + REQUIRE(node->assign_velocity_constraint(3, -12.5) == true); + REQUIRE(node->assign_velocity_constraint(4, 7.5) == true); + + // Apply rotation matrix with Euler angles alpha = 10 deg, beta + // = 30 deg + Eigen::Matrix euler_angles; + euler_angles << 10. * M_PI / 180, 20. * M_PI / 180, 30. * M_PI / 180; + const auto rotation_matrix = + mpm::geometry::rotation_matrix(euler_angles); + node->assign_rotation_matrix(rotation_matrix); + const auto inverse_rotation_matrix = rotation_matrix.inverse(); + + // Apply inclined velocity constraints + node->apply_velocity_constraints(); + + // Check apply constraints + velocity << -14.5068204271, -0.1432759442, 1.4260971922; + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + // Check that the velocity is as specified in local coordinate + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NSolid))(0) == + Approx(-12.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NSolid))(1) == + Approx(7.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NLiquid))(0) == + Approx(-12.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NLiquid))(1) == + Approx(7.5).epsilon(Tolerance)); + + // Check applied constraints on acceleration in the global coordinates + acceleration << 0.1998888554, -1.1336260315, 1.9937880031; + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + + // Check that the acceleration is 0 in local coordinate + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(0) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(1) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NLiquid))(0) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NLiquid))(1) == + Approx(0).epsilon(Tolerance)); + } + + SECTION("Check general velocity constraints in all directions") { + // Apply velocity constraints + REQUIRE(node->assign_velocity_constraint(0, 10.5) == true); + REQUIRE(node->assign_velocity_constraint(1, -12.5) == true); + REQUIRE(node->assign_velocity_constraint(2, 7.5) == true); + REQUIRE(node->assign_velocity_constraint(3, 10.5) == true); + REQUIRE(node->assign_velocity_constraint(4, -12.5) == true); + REQUIRE(node->assign_velocity_constraint(5, 7.5) == true); + + // Apply rotation matrix with Euler angles alpha = -10 deg, beta = 20, + // deg and gamma = -30 deg + Eigen::Matrix euler_angles; + euler_angles << -10. * M_PI / 180, 20. * M_PI / 180, -30. * M_PI / 180; + const auto rotation_matrix = + mpm::geometry::rotation_matrix(euler_angles); + node->assign_rotation_matrix(rotation_matrix); + const auto inverse_rotation_matrix = rotation_matrix.inverse(); + + // Apply constraints + node->apply_velocity_constraints(); + + // Check apply constraints + velocity << 13.351984588153375, -5.717804716697730, 10.572663655835457; + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->velocity(mpm::NodePhase::NSolid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + REQUIRE(node->velocity(mpm::NodePhase::NLiquid)(i) == + Approx(velocity(i)).epsilon(Tolerance)); + } + + // Check that the velocity is as specified in local coordinate + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NSolid))(0) == + Approx(10.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NSolid))(1) == + Approx(-12.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NSolid))(2) == + Approx(7.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NLiquid))(0) == + Approx(10.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NLiquid))(1) == + Approx(-12.5).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->velocity(mpm::NodePhase::NLiquid))(2) == + Approx(7.5).epsilon(Tolerance)); + + // Check apply constraints + acceleration << 0, 0, 0; + for (unsigned i = 0; i < Dim; ++i) { + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + REQUIRE(node->acceleration(mpm::NodePhase::NLiquid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + + // Check that the acceleration is 0 in local coordinate + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(0) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(1) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NLiquid))(0) == + Approx(0).epsilon(Tolerance)); + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NLiquid))(1) == + Approx(0).epsilon(Tolerance)); + } + + SECTION("Check Cartesian friction constraints") { + // Apply friction constraints + REQUIRE(node->assign_friction_constraint(2, 2, 0.3) == true); + // Check out of bounds condition + REQUIRE(node->assign_friction_constraint(4, 1, 0.2) == false); + + // Apply constraints + node->apply_friction_constraints(dt); + + // Check apply constraints + acceleration << 3.939339828220179, 3.939339828220179, 5.; + for (unsigned i = 0; i < acceleration.size(); ++i) + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + + SECTION("Check general friction constraints in 1 direction") { + // Apply friction constraints + REQUIRE(node->assign_friction_constraint(2, 2, 0.3) == true); + + // Apply rotation matrix with Euler angles alpha = 10 deg, beta = 20 deg + // and gamma = 30 deg + Eigen::Matrix euler_angles; + euler_angles << 10. * M_PI / 180, 20. * M_PI / 180, 30. * M_PI / 180; + const auto rotation_matrix = + mpm::geometry::rotation_matrix(euler_angles); + node->assign_rotation_matrix(rotation_matrix); + const auto inverse_rotation_matrix = rotation_matrix.inverse(); + + // Apply inclined velocity constraints + node->apply_friction_constraints(dt); + + // Check applied constraints on acceleration in the global coordinates + acceleration << 4.602895052828914, 4.492575657560740, 4.751301246937935; + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(node->acceleration(mpm::NodePhase::NSolid)(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + + // Check the acceleration in local coordinate + acceleration << 6.878925666702865, 3.365244416454818, 2.302228080558999; + for (unsigned i = 0; i < Dim; ++i) + REQUIRE((inverse_rotation_matrix * + node->acceleration(mpm::NodePhase::NSolid))(i) == + Approx(acceleration(i)).epsilon(Tolerance)); + } + } + + SECTION("Check node material ids") { + // Add material to nodes + node->append_material_id(0); + node->append_material_id(1); + node->append_material_id(4); + node->append_material_id(0); + node->append_material_id(2); + + // Check size of material_ids + REQUIRE(node->material_ids().size() == 4); + + // Check elements of material_ids + std::vector material_ids = {0, 1, 2, 4}; + auto mat_ids = node->material_ids(); + unsigned i = 0; + for (auto mitr = mat_ids.begin(); mitr != mat_ids.end(); ++mitr, ++i) + REQUIRE(*mitr == material_ids.at(i)); + } + } +} diff --git a/tests/node_vector_test.cc b/tests/nodes/node_vector_test.cc similarity index 100% rename from tests/node_vector_test.cc rename to tests/nodes/node_vector_test.cc diff --git a/tests/particle_cell_crossing_test.cc b/tests/particles/particle_cell_crossing_test.cc similarity index 99% rename from tests/particle_cell_crossing_test.cc rename to tests/particles/particle_cell_crossing_test.cc index ea3a1eb35..5da542e5d 100644 --- a/tests/particle_cell_crossing_test.cc +++ b/tests/particles/particle_cell_crossing_test.cc @@ -4,12 +4,12 @@ #include "cell.h" #include "element.h" -#include "hdf5_particle.h" #include "hexahedron_element.h" #include "material.h" #include "mesh.h" #include "node.h" #include "particle.h" +#include "pod_particle.h" #include "quadrilateral_element.h" //! \brief Check particle cell crossing for 2D case diff --git a/tests/particle_serialize_deserialize_test.cc b/tests/particles/particle_serialize_deserialize_test.cc similarity index 96% rename from tests/particle_serialize_deserialize_test.cc rename to tests/particles/particle_serialize_deserialize_test.cc index 6ac2995d5..8058dbcb7 100644 --- a/tests/particle_serialize_deserialize_test.cc +++ b/tests/particles/particle_serialize_deserialize_test.cc @@ -7,12 +7,12 @@ #include "data_types.h" #include "element.h" #include "function_base.h" -#include "hdf5_particle.h" #include "hexahedron_element.h" #include "linear_function.h" #include "material.h" #include "node.h" #include "particle.h" +#include "pod_particle.h" #include "quadrilateral_element.h" //! \brief Check particle class for serialization and deserialization @@ -27,8 +27,8 @@ TEST_CASE("Particle is checked for serialization and deserialization", // Phase const unsigned phase = 0; - // Check initialise particle from HDF5 file - SECTION("Check initialise particle HDF5") { + // Check initialise particle from POD file + SECTION("Check initialise particle POD") { mpm::Index id = 0; const double Tolerance = 1.E-7; // Coordinates @@ -51,7 +51,7 @@ TEST_CASE("Particle is checked for serialization and deserialization", std::vector>> materials; materials.emplace_back(material); - mpm::HDF5Particle h5_particle; + mpm::PODParticle h5_particle; h5_particle.id = 13; h5_particle.mass = 501.5; @@ -112,7 +112,7 @@ TEST_CASE("Particle is checked for serialization and deserialization", h5_particle.svars[0] = 1000.0; // Reinitialise particle from HDF5 data - REQUIRE(particle->initialise_particle(h5_particle, material) == true); + REQUIRE(particle->initialise_particle(h5_particle, materials) == true); // Serialize particle auto buffer = particle->serialize(); diff --git a/tests/particles/particle_serialize_deserialize_twophase_test.cc b/tests/particles/particle_serialize_deserialize_twophase_test.cc new file mode 100644 index 000000000..6c5504e7a --- /dev/null +++ b/tests/particles/particle_serialize_deserialize_twophase_test.cc @@ -0,0 +1,257 @@ +#include + +#include "catch.hpp" + +#include "data_types.h" +#include "material.h" +#include "particle.h" +#include "particle_twophase.h" +#include "pod_particle_twophase.h" + +//! \brief Check particle class for serialization and deserialization +TEST_CASE("Twophase particle is checked for serialization and deserialization", + "[particle][3D][serialize][2Phase]") { + // Dimension + const unsigned Dim = 3; + // Dimension + const unsigned Dof = 3; + // Number of phases + const unsigned Nphases = 2; + // Phase + const unsigned phase = 0; + + // Check initialise particle from POD file + SECTION("Check initialise particle POD") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + // Coordinates + Eigen::Matrix pcoords; + pcoords.setZero(); + + std::shared_ptr> particle = + std::make_shared>(id, pcoords); + + // Assign material + unsigned solid_mid = 1; + unsigned liquid_mid = 2; + // Initialise material + Json jsolid_material; + Json jliquid_material; + jsolid_material["density"] = 1000.; + jsolid_material["youngs_modulus"] = 1.0E+7; + jsolid_material["poisson_ratio"] = 0.3; + jsolid_material["porosity"] = 0.3; + jsolid_material["k_x"] = 0.001; + jsolid_material["k_y"] = 0.001; + jsolid_material["k_z"] = 0.001; + jliquid_material["density"] = 1000.; + jliquid_material["bulk_modulus"] = 2.0E9; + jliquid_material["dynamic_viscosity"] = 8.90E-4; + + auto solid_material = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic3D", std::move(solid_mid), jsolid_material); + auto liquid_material = + Factory, unsigned, const Json&>::instance()->create( + "Newtonian3D", std::move(liquid_mid), jliquid_material); + std::vector>> materials; + materials.emplace_back(solid_material); + materials.emplace_back(liquid_material); + + mpm::PODParticleTwoPhase h5_particle; + h5_particle.id = 13; + h5_particle.mass = 501.5; + + Eigen::Vector3d coords; + coords << 1., 2., 0.; + h5_particle.coord_x = coords[0]; + h5_particle.coord_y = coords[1]; + h5_particle.coord_z = coords[2]; + + Eigen::Vector3d displacement; + displacement << 0.01, 0.02, 0.0; + h5_particle.displacement_x = displacement[0]; + h5_particle.displacement_y = displacement[1]; + h5_particle.displacement_z = displacement[2]; + + Eigen::Vector3d lsize; + lsize << 0.25, 0.5, 0.; + h5_particle.nsize_x = lsize[0]; + h5_particle.nsize_y = lsize[1]; + h5_particle.nsize_z = lsize[2]; + + Eigen::Vector3d velocity; + velocity << 1.5, 2.5, 0.0; + h5_particle.velocity_x = velocity[0]; + h5_particle.velocity_y = velocity[1]; + h5_particle.velocity_z = velocity[2]; + + Eigen::Matrix stress; + stress << 11.5, -12.5, 13.5, 14.5, -15.5, 16.5; + h5_particle.stress_xx = stress[0]; + h5_particle.stress_yy = stress[1]; + h5_particle.stress_zz = stress[2]; + h5_particle.tau_xy = stress[3]; + h5_particle.tau_yz = stress[4]; + h5_particle.tau_xz = stress[5]; + + Eigen::Matrix strain; + strain << 0.115, -0.125, 0.135, 0.145, -0.155, 0.165; + h5_particle.strain_xx = strain[0]; + h5_particle.strain_yy = strain[1]; + h5_particle.strain_zz = strain[2]; + h5_particle.gamma_xy = strain[3]; + h5_particle.gamma_yz = strain[4]; + h5_particle.gamma_xz = strain[5]; + + h5_particle.epsilon_v = strain.head(Dim).sum(); + + h5_particle.status = true; + + h5_particle.cell_id = 1; + + h5_particle.volume = 2.; + + h5_particle.material_id = 1; + + h5_particle.nstate_vars = 0; + + for (unsigned i = 0; i < h5_particle.nstate_vars; ++i) + h5_particle.svars[i] = 0.; + + h5_particle.liquid_mass = 100.1; + + Eigen::Vector3d liquid_velocity; + liquid_velocity << 5.5, 2.1, 4.2; + h5_particle.liquid_velocity_x = liquid_velocity[0]; + h5_particle.liquid_velocity_y = liquid_velocity[1]; + h5_particle.liquid_velocity_z = liquid_velocity[2]; + + h5_particle.porosity = 0.33; + + h5_particle.liquid_saturation = 1.; + + h5_particle.liquid_material_id = 2; + + h5_particle.nliquid_state_vars = 1; + + for (unsigned i = 0; i < h5_particle.nliquid_state_vars; ++i) + h5_particle.liquid_svars[i] = 0.; + + // Reinitialise particle from POD data + REQUIRE(particle->initialise_particle(h5_particle, materials) == true); + + // Serialize particle + auto buffer = particle->serialize(); + REQUIRE(buffer.size() > 0); + + // Deserialize particle + std::shared_ptr> rparticle = + std::make_shared>(id, pcoords); + + REQUIRE_NOTHROW(rparticle->deserialize(buffer, materials)); + + // Check particle id + REQUIRE(particle->id() == particle->id()); + // Check particle mass + REQUIRE(particle->mass() == rparticle->mass()); + // Check particle volume + REQUIRE(particle->volume() == rparticle->volume()); + // Check particle mass density + REQUIRE(particle->mass_density() == rparticle->mass_density()); + // Check particle status + REQUIRE(particle->status() == rparticle->status()); + + // Check for coordinates + auto coordinates = rparticle->coordinates(); + REQUIRE(coordinates.size() == Dim); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Check for displacement + auto pdisplacement = rparticle->displacement(); + REQUIRE(pdisplacement.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pdisplacement(i) == Approx(displacement(i)).epsilon(Tolerance)); + + // Check for size + auto size = rparticle->natural_size(); + REQUIRE(size.size() == Dim); + for (unsigned i = 0; i < size.size(); ++i) + REQUIRE(size(i) == Approx(lsize(i)).epsilon(Tolerance)); + + // Check velocity + auto pvelocity = rparticle->velocity(); + REQUIRE(pvelocity.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pvelocity(i) == Approx(velocity(i)).epsilon(Tolerance)); + + // Check stress + auto pstress = rparticle->stress(); + REQUIRE(pstress.size() == stress.size()); + for (unsigned i = 0; i < stress.size(); ++i) + REQUIRE(pstress(i) == Approx(stress(i)).epsilon(Tolerance)); + + // Check strain + auto pstrain = rparticle->strain(); + REQUIRE(pstrain.size() == strain.size()); + for (unsigned i = 0; i < strain.size(); ++i) + REQUIRE(pstrain(i) == Approx(strain(i)).epsilon(Tolerance)); + + // Check particle volumetric strain centroid + REQUIRE(particle->volumetric_strain_centroid() == + rparticle->volumetric_strain_centroid()); + + // Check cell id + REQUIRE(particle->cell_id() == rparticle->cell_id()); + + // Check material id + REQUIRE(particle->material_id() == rparticle->material_id()); + + // Check liquid mass + REQUIRE(particle->liquid_mass() == rparticle->liquid_mass()); + + // Check liquid velocity + auto pliquid_velocity = rparticle->liquid_velocity(); + REQUIRE(pliquid_velocity.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pliquid_velocity(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + + // Check porosity + REQUIRE(particle->porosity() == rparticle->porosity()); + + // Check liquid material id + REQUIRE(particle->material_id(mpm::ParticlePhase::Liquid) == + rparticle->material_id(mpm::ParticlePhase::Liquid)); + + // Check state variables + for (unsigned phase = 0; phase < materials.size(); phase++) { + REQUIRE(particle->state_variables(phase).size() == + rparticle->state_variables(phase).size()); + auto state_variables = materials[phase]->state_variables(); + for (const auto& state_var : state_variables) { + REQUIRE(particle->state_variable(state_var, phase) == + rparticle->state_variable(state_var, phase)); + } + } + + SECTION("Performance benchmarks") { + // Number of iterations + unsigned niterations = 1000; + + // Serialization benchmarks + auto serialize_start = std::chrono::steady_clock::now(); + for (unsigned i = 0; i < niterations; ++i) { + // Serialize particle + auto buffer = particle->serialize(); + // Deserialize particle + std::shared_ptr> rparticle = + std::make_shared>(id, pcoords); + + REQUIRE_NOTHROW(rparticle->deserialize(buffer, materials)); + } + auto serialize_end = std::chrono::steady_clock::now(); + } + } +} diff --git a/tests/particle_test.cc b/tests/particles/particle_test.cc similarity index 92% rename from tests/particle_test.cc rename to tests/particles/particle_test.cc index 9059a8940..8352c6926 100644 --- a/tests/particle_test.cc +++ b/tests/particles/particle_test.cc @@ -5,12 +5,12 @@ #include "cell.h" #include "element.h" #include "function_base.h" -#include "hdf5_particle.h" #include "hexahedron_element.h" #include "linear_function.h" #include "material.h" #include "node.h" #include "particle.h" +#include "pod_particle.h" #include "quadrilateral_element.h" //! \brief Check particle class for 1D case @@ -256,13 +256,13 @@ TEST_CASE("Particle is checked for 1D case", "[particle][1D]") { } } - SECTION("Check initialise particle HDF5") { + SECTION("Check initialise particle POD") { mpm::Index id = 0; const double Tolerance = 1.E-7; std::shared_ptr> particle = std::make_shared>(id, coords); - mpm::HDF5Particle h5_particle; + mpm::PODParticle h5_particle; h5_particle.id = 13; h5_particle.mass = 501.5; @@ -318,7 +318,7 @@ TEST_CASE("Particle is checked for 1D case", "[particle][1D]") { h5_particle.material_id = 1; - // Reinitialise particle from HDF5 data + // Reinitialise particle from POD data REQUIRE(particle->initialise_particle(h5_particle) == true); // Check particle id @@ -377,62 +377,65 @@ TEST_CASE("Particle is checked for 1D case", "[particle][1D]") { // Check material id REQUIRE(particle->material_id() == h5_particle.material_id); - // Write Particle HDF5 data - const auto h5_test = particle->hdf5(); + // Write Particle POD data + auto pod_test = std::static_pointer_cast(particle->pod()); - REQUIRE(h5_particle.id == h5_test.id); - REQUIRE(h5_particle.mass == h5_test.mass); + REQUIRE(h5_particle.id == pod_test->id); + REQUIRE(h5_particle.mass == pod_test->mass); - REQUIRE(h5_particle.coord_x == Approx(h5_test.coord_x).epsilon(Tolerance)); - REQUIRE(h5_particle.coord_y == Approx(h5_test.coord_y).epsilon(Tolerance)); - REQUIRE(h5_particle.coord_z == Approx(h5_test.coord_z).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_x == + Approx(pod_test->coord_x).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_y == + Approx(pod_test->coord_y).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_z == + Approx(pod_test->coord_z).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_x == - Approx(h5_test.displacement_x).epsilon(Tolerance)); + Approx(pod_test->displacement_x).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_y == - Approx(h5_test.displacement_y).epsilon(Tolerance)); + Approx(pod_test->displacement_y).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_z == - Approx(h5_test.displacement_z).epsilon(Tolerance)); + Approx(pod_test->displacement_z).epsilon(Tolerance)); - REQUIRE(h5_particle.nsize_x == h5_test.nsize_x); - REQUIRE(h5_particle.nsize_y == h5_test.nsize_y); - REQUIRE(h5_particle.nsize_z == h5_test.nsize_z); + REQUIRE(h5_particle.nsize_x == pod_test->nsize_x); + REQUIRE(h5_particle.nsize_y == pod_test->nsize_y); + REQUIRE(h5_particle.nsize_z == pod_test->nsize_z); REQUIRE(h5_particle.velocity_x == - Approx(h5_test.velocity_x).epsilon(Tolerance)); + Approx(pod_test->velocity_x).epsilon(Tolerance)); REQUIRE(h5_particle.velocity_y == - Approx(h5_test.velocity_y).epsilon(Tolerance)); + Approx(pod_test->velocity_y).epsilon(Tolerance)); REQUIRE(h5_particle.velocity_z == - Approx(h5_test.velocity_z).epsilon(Tolerance)); + Approx(pod_test->velocity_z).epsilon(Tolerance)); REQUIRE(h5_particle.stress_xx == - Approx(h5_test.stress_xx).epsilon(Tolerance)); + Approx(pod_test->stress_xx).epsilon(Tolerance)); REQUIRE(h5_particle.stress_yy == - Approx(h5_test.stress_yy).epsilon(Tolerance)); + Approx(pod_test->stress_yy).epsilon(Tolerance)); REQUIRE(h5_particle.stress_zz == - Approx(h5_test.stress_zz).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_xy == Approx(h5_test.tau_xy).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_yz == Approx(h5_test.tau_yz).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_xz == Approx(h5_test.tau_xz).epsilon(Tolerance)); + Approx(pod_test->stress_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xy == Approx(pod_test->tau_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_yz == Approx(pod_test->tau_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xz == Approx(pod_test->tau_xz).epsilon(Tolerance)); REQUIRE(h5_particle.strain_xx == - Approx(h5_test.strain_xx).epsilon(Tolerance)); + Approx(pod_test->strain_xx).epsilon(Tolerance)); REQUIRE(h5_particle.strain_yy == - Approx(h5_test.strain_yy).epsilon(Tolerance)); + Approx(pod_test->strain_yy).epsilon(Tolerance)); REQUIRE(h5_particle.strain_zz == - Approx(h5_test.strain_zz).epsilon(Tolerance)); + Approx(pod_test->strain_zz).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_xy == - Approx(h5_test.gamma_xy).epsilon(Tolerance)); + Approx(pod_test->gamma_xy).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_yz == - Approx(h5_test.gamma_yz).epsilon(Tolerance)); + Approx(pod_test->gamma_yz).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_xz == - Approx(h5_test.gamma_xz).epsilon(Tolerance)); + Approx(pod_test->gamma_xz).epsilon(Tolerance)); REQUIRE(h5_particle.epsilon_v == - Approx(h5_test.epsilon_v).epsilon(Tolerance)); - REQUIRE(h5_particle.status == h5_test.status); - REQUIRE(h5_particle.cell_id == h5_test.cell_id); - REQUIRE(h5_particle.material_id == h5_test.material_id); + Approx(pod_test->epsilon_v).epsilon(Tolerance)); + REQUIRE(h5_particle.status == pod_test->status); + REQUIRE(h5_particle.cell_id == pod_test->cell_id); + REQUIRE(h5_particle.material_id == pod_test->material_id); } } @@ -1245,9 +1248,14 @@ TEST_CASE("Particle is checked for 2D case", "[particle][2D]") { SECTION("Assign state variables") { // Assign material properties REQUIRE(particle->assign_material(mc_material) == true); - // Assing state variables + // Assign state variables REQUIRE(particle->assign_material_state_vars(state_variables, mc_material) == true); + // Assign and read a state variable + REQUIRE_NOTHROW(particle->assign_state_variable("phi", 30.)); + REQUIRE(particle->state_variable("phi") == 30.); + // Assign and read pressure though MC does not contain pressure + REQUIRE(std::isnan(particle->pressure()) == true); } SECTION("Assign state variables fail on state variables size") { @@ -1265,7 +1273,7 @@ TEST_CASE("Particle is checked for 2D case", "[particle][2D]") { // Assign material properties REQUIRE(particle->assign_material(newtonian_material) == true); - // Assing state variables + // Assign state variables REQUIRE(particle->assign_material_state_vars(state_variables, mc_material) == false); } @@ -1285,7 +1293,7 @@ TEST_CASE("Particle is checked for 2D case", "[particle][2D]") { // Assign material properties REQUIRE(particle->assign_material(newtonian_material) == true); - // Assing state variables + // Assign state variables REQUIRE(particle->assign_material_state_vars(state_variables, mc_material) == false); } @@ -1393,14 +1401,14 @@ TEST_CASE("Particle is checked for 2D case", "[particle][2D]") { } } - // Check initialise particle from HDF5 file - SECTION("Check initialise particle HDF5") { + // Check initialise particle from POD file + SECTION("Check initialise particle POD") { mpm::Index id = 0; const double Tolerance = 1.E-7; std::shared_ptr> particle = std::make_shared>(id, coords); - mpm::HDF5Particle h5_particle; + mpm::PODParticle h5_particle; h5_particle.id = 13; h5_particle.mass = 501.5; @@ -1456,7 +1464,7 @@ TEST_CASE("Particle is checked for 2D case", "[particle][2D]") { h5_particle.material_id = 1; - // Reinitialise particle from HDF5 data + // Reinitialise particle from POD data REQUIRE(particle->initialise_particle(h5_particle) == true); // Check particle id @@ -1515,62 +1523,65 @@ TEST_CASE("Particle is checked for 2D case", "[particle][2D]") { // Check material id REQUIRE(particle->material_id() == h5_particle.material_id); - // Write Particle HDF5 data - const auto h5_test = particle->hdf5(); + // Write Particle POD data + auto pod_test = std::static_pointer_cast(particle->pod()); - REQUIRE(h5_particle.id == h5_test.id); - REQUIRE(h5_particle.mass == h5_test.mass); + REQUIRE(h5_particle.id == pod_test->id); + REQUIRE(h5_particle.mass == pod_test->mass); - REQUIRE(h5_particle.coord_x == Approx(h5_test.coord_x).epsilon(Tolerance)); - REQUIRE(h5_particle.coord_y == Approx(h5_test.coord_y).epsilon(Tolerance)); - REQUIRE(h5_particle.coord_z == Approx(h5_test.coord_z).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_x == + Approx(pod_test->coord_x).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_y == + Approx(pod_test->coord_y).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_z == + Approx(pod_test->coord_z).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_x == - Approx(h5_test.displacement_x).epsilon(Tolerance)); + Approx(pod_test->displacement_x).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_y == - Approx(h5_test.displacement_y).epsilon(Tolerance)); + Approx(pod_test->displacement_y).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_z == - Approx(h5_test.displacement_z).epsilon(Tolerance)); + Approx(pod_test->displacement_z).epsilon(Tolerance)); - REQUIRE(h5_particle.nsize_x == h5_test.nsize_x); - REQUIRE(h5_particle.nsize_y == h5_test.nsize_y); - REQUIRE(h5_particle.nsize_z == h5_test.nsize_z); + REQUIRE(h5_particle.nsize_x == pod_test->nsize_x); + REQUIRE(h5_particle.nsize_y == pod_test->nsize_y); + REQUIRE(h5_particle.nsize_z == pod_test->nsize_z); REQUIRE(h5_particle.velocity_x == - Approx(h5_test.velocity_x).epsilon(Tolerance)); + Approx(pod_test->velocity_x).epsilon(Tolerance)); REQUIRE(h5_particle.velocity_y == - Approx(h5_test.velocity_y).epsilon(Tolerance)); + Approx(pod_test->velocity_y).epsilon(Tolerance)); REQUIRE(h5_particle.velocity_z == - Approx(h5_test.velocity_z).epsilon(Tolerance)); + Approx(pod_test->velocity_z).epsilon(Tolerance)); REQUIRE(h5_particle.stress_xx == - Approx(h5_test.stress_xx).epsilon(Tolerance)); + Approx(pod_test->stress_xx).epsilon(Tolerance)); REQUIRE(h5_particle.stress_yy == - Approx(h5_test.stress_yy).epsilon(Tolerance)); + Approx(pod_test->stress_yy).epsilon(Tolerance)); REQUIRE(h5_particle.stress_zz == - Approx(h5_test.stress_zz).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_xy == Approx(h5_test.tau_xy).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_yz == Approx(h5_test.tau_yz).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_xz == Approx(h5_test.tau_xz).epsilon(Tolerance)); + Approx(pod_test->stress_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xy == Approx(pod_test->tau_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_yz == Approx(pod_test->tau_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xz == Approx(pod_test->tau_xz).epsilon(Tolerance)); REQUIRE(h5_particle.strain_xx == - Approx(h5_test.strain_xx).epsilon(Tolerance)); + Approx(pod_test->strain_xx).epsilon(Tolerance)); REQUIRE(h5_particle.strain_yy == - Approx(h5_test.strain_yy).epsilon(Tolerance)); + Approx(pod_test->strain_yy).epsilon(Tolerance)); REQUIRE(h5_particle.strain_zz == - Approx(h5_test.strain_zz).epsilon(Tolerance)); + Approx(pod_test->strain_zz).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_xy == - Approx(h5_test.gamma_xy).epsilon(Tolerance)); + Approx(pod_test->gamma_xy).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_yz == - Approx(h5_test.gamma_yz).epsilon(Tolerance)); + Approx(pod_test->gamma_yz).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_xz == - Approx(h5_test.gamma_xz).epsilon(Tolerance)); + Approx(pod_test->gamma_xz).epsilon(Tolerance)); REQUIRE(h5_particle.epsilon_v == - Approx(h5_test.epsilon_v).epsilon(Tolerance)); - REQUIRE(h5_particle.status == h5_test.status); - REQUIRE(h5_particle.cell_id == h5_test.cell_id); - REQUIRE(h5_particle.material_id == h5_test.material_id); + Approx(pod_test->epsilon_v).epsilon(Tolerance)); + REQUIRE(h5_particle.status == pod_test->status); + REQUIRE(h5_particle.cell_id == pod_test->cell_id); + REQUIRE(h5_particle.material_id == pod_test->material_id); } // Check particle's material id maping to nodes @@ -2553,9 +2564,14 @@ TEST_CASE("Particle is checked for 3D case", "[particle][3D]") { SECTION("Assign state variables") { // Assign material properties REQUIRE(particle->assign_material(mc_material) == true); - // Assing state variables + // Assign state variables REQUIRE(particle->assign_material_state_vars(state_variables, mc_material) == true); + // Assign and read a state variable + REQUIRE_NOTHROW(particle->assign_state_variable("phi", 30.)); + REQUIRE(particle->state_variable("phi") == 30.); + // Assign and read pressure though MC does not contain pressure + REQUIRE(std::isnan(particle->pressure()) == true); } SECTION("Assign state variables fail on state variables size") { @@ -2573,7 +2589,7 @@ TEST_CASE("Particle is checked for 3D case", "[particle][3D]") { // Assign material properties REQUIRE(particle->assign_material(newtonian_material) == true); - // Assing state variables + // Assign state variables REQUIRE(particle->assign_material_state_vars(state_variables, mc_material) == false); } @@ -2593,7 +2609,7 @@ TEST_CASE("Particle is checked for 3D case", "[particle][3D]") { // Assign material properties REQUIRE(particle->assign_material(newtonian_material) == true); - // Assing state variables + // Assign state variables REQUIRE(particle->assign_material_state_vars(state_variables, mc_material) == false); } @@ -2742,14 +2758,14 @@ TEST_CASE("Particle is checked for 3D case", "[particle][3D]") { } } - // Check initialise particle from HDF5 file - SECTION("Check initialise particle HDF5") { + // Check initialise particle from POD file + SECTION("Check initialise particle POD") { mpm::Index id = 0; const double Tolerance = 1.E-7; std::shared_ptr> particle = std::make_shared>(id, coords); - mpm::HDF5Particle h5_particle; + mpm::PODParticle h5_particle; h5_particle.id = 13; h5_particle.mass = 501.5; @@ -2805,7 +2821,7 @@ TEST_CASE("Particle is checked for 3D case", "[particle][3D]") { h5_particle.material_id = 1; - // Reinitialise particle from HDF5 data + // Reinitialise particle from POD data REQUIRE(particle->initialise_particle(h5_particle) == true); // Check particle id @@ -2865,62 +2881,65 @@ TEST_CASE("Particle is checked for 3D case", "[particle][3D]") { // Check material id REQUIRE(particle->material_id() == h5_particle.material_id); - // Write Particle HDF5 data - const auto h5_test = particle->hdf5(); + // Write Particle POD data + auto pod_test = std::static_pointer_cast(particle->pod()); - REQUIRE(h5_particle.id == h5_test.id); - REQUIRE(h5_particle.mass == h5_test.mass); + REQUIRE(h5_particle.id == pod_test->id); + REQUIRE(h5_particle.mass == pod_test->mass); - REQUIRE(h5_particle.coord_x == Approx(h5_test.coord_x).epsilon(Tolerance)); - REQUIRE(h5_particle.coord_y == Approx(h5_test.coord_y).epsilon(Tolerance)); - REQUIRE(h5_particle.coord_z == Approx(h5_test.coord_z).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_x == + Approx(pod_test->coord_x).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_y == + Approx(pod_test->coord_y).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_z == + Approx(pod_test->coord_z).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_x == - Approx(h5_test.displacement_x).epsilon(Tolerance)); + Approx(pod_test->displacement_x).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_y == - Approx(h5_test.displacement_y).epsilon(Tolerance)); + Approx(pod_test->displacement_y).epsilon(Tolerance)); REQUIRE(h5_particle.displacement_z == - Approx(h5_test.displacement_z).epsilon(Tolerance)); + Approx(pod_test->displacement_z).epsilon(Tolerance)); - REQUIRE(h5_particle.nsize_x == h5_test.nsize_x); - REQUIRE(h5_particle.nsize_y == h5_test.nsize_y); - REQUIRE(h5_particle.nsize_z == h5_test.nsize_z); + REQUIRE(h5_particle.nsize_x == pod_test->nsize_x); + REQUIRE(h5_particle.nsize_y == pod_test->nsize_y); + REQUIRE(h5_particle.nsize_z == pod_test->nsize_z); REQUIRE(h5_particle.velocity_x == - Approx(h5_test.velocity_x).epsilon(Tolerance)); + Approx(pod_test->velocity_x).epsilon(Tolerance)); REQUIRE(h5_particle.velocity_y == - Approx(h5_test.velocity_y).epsilon(Tolerance)); + Approx(pod_test->velocity_y).epsilon(Tolerance)); REQUIRE(h5_particle.velocity_z == - Approx(h5_test.velocity_z).epsilon(Tolerance)); + Approx(pod_test->velocity_z).epsilon(Tolerance)); REQUIRE(h5_particle.stress_xx == - Approx(h5_test.stress_xx).epsilon(Tolerance)); + Approx(pod_test->stress_xx).epsilon(Tolerance)); REQUIRE(h5_particle.stress_yy == - Approx(h5_test.stress_yy).epsilon(Tolerance)); + Approx(pod_test->stress_yy).epsilon(Tolerance)); REQUIRE(h5_particle.stress_zz == - Approx(h5_test.stress_zz).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_xy == Approx(h5_test.tau_xy).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_yz == Approx(h5_test.tau_yz).epsilon(Tolerance)); - REQUIRE(h5_particle.tau_xz == Approx(h5_test.tau_xz).epsilon(Tolerance)); + Approx(pod_test->stress_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xy == Approx(pod_test->tau_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_yz == Approx(pod_test->tau_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xz == Approx(pod_test->tau_xz).epsilon(Tolerance)); REQUIRE(h5_particle.strain_xx == - Approx(h5_test.strain_xx).epsilon(Tolerance)); + Approx(pod_test->strain_xx).epsilon(Tolerance)); REQUIRE(h5_particle.strain_yy == - Approx(h5_test.strain_yy).epsilon(Tolerance)); + Approx(pod_test->strain_yy).epsilon(Tolerance)); REQUIRE(h5_particle.strain_zz == - Approx(h5_test.strain_zz).epsilon(Tolerance)); + Approx(pod_test->strain_zz).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_xy == - Approx(h5_test.gamma_xy).epsilon(Tolerance)); + Approx(pod_test->gamma_xy).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_yz == - Approx(h5_test.gamma_yz).epsilon(Tolerance)); + Approx(pod_test->gamma_yz).epsilon(Tolerance)); REQUIRE(h5_particle.gamma_xz == - Approx(h5_test.gamma_xz).epsilon(Tolerance)); + Approx(pod_test->gamma_xz).epsilon(Tolerance)); REQUIRE(h5_particle.epsilon_v == - Approx(h5_test.epsilon_v).epsilon(Tolerance)); - REQUIRE(h5_particle.status == h5_test.status); - REQUIRE(h5_particle.cell_id == h5_test.cell_id); - REQUIRE(h5_particle.material_id == h5_test.material_id); + Approx(pod_test->epsilon_v).epsilon(Tolerance)); + REQUIRE(h5_particle.status == pod_test->status); + REQUIRE(h5_particle.cell_id == pod_test->cell_id); + REQUIRE(h5_particle.material_id == pod_test->material_id); } // Check particle's material id maping to nodes @@ -3026,4 +3045,40 @@ TEST_CASE("Particle is checked for 3D case", "[particle][3D]") { REQUIRE(*mitr == material_ids.at(i)); } } + + //! Check derived particle functions (expecting throws) + SECTION("Particle illegal derived functions access") { + mpm::Index id = 0; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + + // Input variables + const double dt = 0.1; + Eigen::VectorXd vectordim; + vectordim.resize(Dim); + std::map reference_points; + + // Specific functions for TwoPhaseParticle. + // Expecting throws if called from Particle. + REQUIRE_THROWS(particle->assign_projection_parameter(1)); + REQUIRE_THROWS(particle->map_laplacian_to_cell()); + REQUIRE_THROWS(particle->map_poisson_right_to_cell()); + REQUIRE_THROWS(particle->map_correction_matrix_to_cell()); + REQUIRE_THROWS(particle->compute_updated_pressure()); + REQUIRE_THROWS(particle->update_porosity(dt)); + REQUIRE_THROWS(particle->assign_saturation_degree()); + REQUIRE_THROWS(particle->assign_liquid_velocity(vectordim)); + REQUIRE_THROWS(particle->compute_pore_pressure(dt)); + REQUIRE_THROWS(particle->map_drag_force_coefficient()); + REQUIRE_THROWS(particle->compute_pore_pressure(dt)); + REQUIRE_THROWS(particle->initialise_pore_pressure_watertable( + 1, 0, vectordim, reference_points)); + REQUIRE_THROWS(particle->assign_porosity()); + REQUIRE_THROWS(particle->assign_permeability()); + REQUIRE_THROWS(particle->liquid_mass()); + REQUIRE_THROWS(particle->liquid_velocity()); + REQUIRE_THROWS(particle->porosity()); + REQUIRE_THROWS(particle->map_drag_matrix_to_cell()); + } } diff --git a/tests/particle_traction_test.cc b/tests/particles/particle_traction_test.cc similarity index 100% rename from tests/particle_traction_test.cc rename to tests/particles/particle_traction_test.cc diff --git a/tests/particles/particle_twophase_test.cc b/tests/particles/particle_twophase_test.cc new file mode 100644 index 000000000..64b9292d2 --- /dev/null +++ b/tests/particles/particle_twophase_test.cc @@ -0,0 +1,3284 @@ +#include + +#include "catch.hpp" + +#include "cell.h" +#include "element.h" +#include "function_base.h" +#include "hexahedron_element.h" +#include "linear_function.h" +#include "material.h" +#include "node.h" +#include "particle.h" +#include "particle_twophase.h" +#include "pod_particle.h" +#include "quadrilateral_element.h" + +//! \brief Check twophase particle class for 1D case +TEST_CASE("TwoPhase Particle is checked for 1D case", + "[particle][1D][2Phase]") { + // Dimension + const unsigned Dim = 1; + // Dimension + const unsigned Dof = 1; + // Number of phases + const unsigned Nphases = 2; + // Json property + Json jfunctionproperties; + jfunctionproperties["id"] = 0; + std::vector x_values{{0.0, 0.5, 1.0}}; + std::vector fx_values{{0.0, 1.0, 1.0}}; + jfunctionproperties["xvalues"] = x_values; + jfunctionproperties["fxvalues"] = fx_values; + + // math function + std::shared_ptr mfunction = + std::make_shared(0, jfunctionproperties); + + // Coordinates + Eigen::Matrix coords; + coords.setZero(); + + //! Check for id = 0 + SECTION("TwoPhase Particle id is zero") { + mpm::Index id = 0; + std::shared_ptr> particle = + std::make_shared>(id, coords); + REQUIRE(particle->id() == 0); + REQUIRE(particle->status() == true); + } + + SECTION("TwoPhase Particle id is positive") { + //! Check for id is a positive value + mpm::Index id = std::numeric_limits::max(); + std::shared_ptr> particle = + std::make_shared>(id, coords); + REQUIRE(particle->id() == std::numeric_limits::max()); + REQUIRE(particle->status() == true); + } + + //! Construct with id, coordinates and status + SECTION("TwoPhase Particle with id, coordinates, and status") { + mpm::Index id = 0; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + REQUIRE(particle->id() == 0); + REQUIRE(particle->status() == true); + particle->assign_status(false); + REQUIRE(particle->status() == false); + } + + //! Test coordinates function + SECTION("coordinates function is checked") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + + std::shared_ptr> particle = + std::make_shared>(id, coords); + + // Check for coordinates being zero + auto coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + REQUIRE(coordinates.size() == Dim); + + // Check for negative value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = -1. * std::numeric_limits::max(); + particle->assign_coordinates(coords); + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + + // Check for positive value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = std::numeric_limits::max(); + particle->assign_coordinates(coords); + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + } + + //! Test initialise particle stresses + SECTION("TwoPhase Particle with initial stress") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + //! Test initialise particle stresses + Eigen::Matrix stress = + Eigen::Matrix::Constant(5.7); + particle->initial_stress(stress); + REQUIRE(particle->stress().size() == stress.size()); + auto pstress = particle->stress(); + for (unsigned i = 0; i < pstress.size(); ++i) + REQUIRE(pstress[i] == Approx(stress[i]).epsilon(Tolerance)); + + auto pstress_data = particle->tensor_data("stresses"); + for (unsigned i = 0; i < pstress_data.size(); ++i) + REQUIRE(pstress_data[i] == Approx(stress[i]).epsilon(Tolerance)); + } + + //! Test particles velocity constraints + SECTION("TwoPhase Particle with velocity constraints") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + // Apply constraints for solid phase + particle->apply_particle_velocity_constraints(0, 10.5); + particle->apply_particle_velocity_constraints(1, 20.5); + + // Check apply constraints + REQUIRE(particle->velocity()(0) == Approx(10.5).epsilon(Tolerance)); + REQUIRE(particle->liquid_velocity()(0) == Approx(20.5).epsilon(Tolerance)); + } + + SECTION("Check particle properties") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> particle = + std::make_shared>(id, coords); + + // Check mass + REQUIRE(particle->mass() == Approx(0.0).epsilon(Tolerance)); + double mass = 100.5; + particle->assign_mass(mass); + REQUIRE(particle->mass() == Approx(100.5).epsilon(Tolerance)); + + // Check stress + Eigen::Matrix stress; + for (unsigned i = 0; i < stress.size(); ++i) stress(i) = 17.51; + + for (unsigned i = 0; i < stress.size(); ++i) + REQUIRE(particle->stress()(i) == Approx(0.).epsilon(Tolerance)); + + // Check velocity + Eigen::VectorXd velocity; + velocity.resize(Dim); + for (unsigned i = 0; i < velocity.size(); ++i) velocity(i) = 17.51; + + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == Approx(0.).epsilon(Tolerance)); + + REQUIRE(particle->assign_velocity(velocity) == true); + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == Approx(17.51).epsilon(Tolerance)); + + // Assign volume + REQUIRE(particle->assign_volume(0.0) == false); + REQUIRE(particle->assign_volume(-5.0) == false); + REQUIRE(particle->assign_volume(2.0) == true); + // Check volume + REQUIRE(particle->volume() == Approx(2.0).epsilon(Tolerance)); + // Traction + double traction = 65.32; + const unsigned Direction = 0; + // Check traction + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + // Try with a null math fuction ptr + REQUIRE(particle->assign_traction(Direction, traction) == true); + + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(particle->traction()(i) == Approx(traction).epsilon(Tolerance)); + else + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + } + + // Check for incorrect direction + const unsigned wrong_dir = 2; + REQUIRE(particle->assign_traction(wrong_dir, traction) == false); + + // Check again to ensure value hasn't been updated + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(particle->traction()(i) == Approx(traction).epsilon(Tolerance)); + else + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + } + } + + SECTION("Check initialise particle POD") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> particle = + std::make_shared>(id, coords); + + mpm::PODParticleTwoPhase h5_particle; + h5_particle.id = 13; + h5_particle.mass = 501.5; + + Eigen::Vector3d coords; + coords << 1., 0., 0.; + h5_particle.coord_x = coords[0]; + h5_particle.coord_y = coords[1]; + h5_particle.coord_z = coords[2]; + + Eigen::Vector3d displacement; + displacement << 0.01, 0.0, 0.0; + h5_particle.displacement_x = displacement[0]; + h5_particle.displacement_y = displacement[1]; + h5_particle.displacement_z = displacement[2]; + + Eigen::Vector3d lsize; + lsize << 0.25, 0.0, 0.0; + h5_particle.nsize_x = lsize[0]; + h5_particle.nsize_y = lsize[1]; + h5_particle.nsize_z = lsize[2]; + + Eigen::Vector3d velocity; + velocity << 1.5, 0., 0.; + h5_particle.velocity_x = velocity[0]; + h5_particle.velocity_y = velocity[1]; + h5_particle.velocity_z = velocity[2]; + + Eigen::Matrix stress; + stress << 11.5, -12.5, 13.5, 14.5, -15.5, 16.5; + h5_particle.stress_xx = stress[0]; + h5_particle.stress_yy = stress[1]; + h5_particle.stress_zz = stress[2]; + h5_particle.tau_xy = stress[3]; + h5_particle.tau_yz = stress[4]; + h5_particle.tau_xz = stress[5]; + + Eigen::Matrix strain; + strain << 0.115, -0.125, 0.135, 0.145, -0.155, 0.165; + h5_particle.strain_xx = strain[0]; + h5_particle.strain_yy = strain[1]; + h5_particle.strain_zz = strain[2]; + h5_particle.gamma_xy = strain[3]; + h5_particle.gamma_yz = strain[4]; + h5_particle.gamma_xz = strain[5]; + + h5_particle.epsilon_v = strain.head(Dim).sum(); + + h5_particle.status = true; + + h5_particle.cell_id = 1; + + h5_particle.volume = 2.; + + h5_particle.material_id = 1; + + h5_particle.liquid_mass = 100.1; + + Eigen::Vector3d liquid_velocity; + liquid_velocity << 5.5, 0., 0.; + h5_particle.liquid_velocity_x = liquid_velocity[0]; + h5_particle.liquid_velocity_y = liquid_velocity[1]; + h5_particle.liquid_velocity_z = liquid_velocity[2]; + + h5_particle.porosity = 0.33; + + h5_particle.liquid_saturation = 1.; + + h5_particle.liquid_material_id = 2; + + // Reinitialise particle from POD data + REQUIRE(particle->initialise_particle(h5_particle) == true); + + // Check particle id + REQUIRE(particle->id() == h5_particle.id); + // Check particle mass + REQUIRE(particle->mass() == h5_particle.mass); + // Check particle volume + REQUIRE(particle->volume() == h5_particle.volume); + // Check particle mass density + REQUIRE(particle->mass_density() == h5_particle.mass / h5_particle.volume); + // Check particle status + REQUIRE(particle->status() == h5_particle.status); + + // Check for coordinates + auto coordinates = particle->coordinates(); + REQUIRE(coordinates.size() == Dim); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Check for displacement + auto pdisplacement = particle->displacement(); + REQUIRE(pdisplacement.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pdisplacement(i) == Approx(displacement(i)).epsilon(Tolerance)); + + // Check for size + auto size = particle->natural_size(); + REQUIRE(size.size() == Dim); + for (unsigned i = 0; i < size.size(); ++i) + REQUIRE(size(i) == Approx(lsize(i)).epsilon(Tolerance)); + + // Check velocity + auto pvelocity = particle->velocity(); + REQUIRE(pvelocity.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pvelocity(i) == Approx(velocity(i)).epsilon(Tolerance)); + + // Check stress + auto pstress = particle->stress(); + REQUIRE(pstress.size() == stress.size()); + for (unsigned i = 0; i < stress.size(); ++i) + REQUIRE(pstress(i) == Approx(stress(i)).epsilon(Tolerance)); + + // Check strain + auto pstrain = particle->strain(); + REQUIRE(pstrain.size() == strain.size()); + for (unsigned i = 0; i < strain.size(); ++i) + REQUIRE(pstrain(i) == Approx(strain(i)).epsilon(Tolerance)); + + // Check particle volumetric strain centroid + REQUIRE(particle->volumetric_strain_centroid() == h5_particle.epsilon_v); + + // Check cell id + REQUIRE(particle->cell_id() == h5_particle.cell_id); + + // Check material id + REQUIRE(particle->material_id() == h5_particle.material_id); + + // Check liquid mass + REQUIRE(particle->liquid_mass() == h5_particle.liquid_mass); + + // Check liquid velocity + auto pliquid_velocity = particle->liquid_velocity(); + REQUIRE(pliquid_velocity.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pliquid_velocity(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + + // Check porosity + REQUIRE(particle->porosity() == h5_particle.porosity); + + // Check liquid material id + REQUIRE(particle->material_id(mpm::ParticlePhase::Liquid) == + h5_particle.liquid_material_id); + + // Write Particle POD data + auto pod_test = + std::static_pointer_cast(particle->pod()); + + REQUIRE(h5_particle.id == pod_test->id); + REQUIRE(h5_particle.mass == pod_test->mass); + + REQUIRE(h5_particle.coord_x == + Approx(pod_test->coord_x).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_y == + Approx(pod_test->coord_y).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_z == + Approx(pod_test->coord_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.displacement_x == + Approx(pod_test->displacement_x).epsilon(Tolerance)); + REQUIRE(h5_particle.displacement_y == + Approx(pod_test->displacement_y).epsilon(Tolerance)); + REQUIRE(h5_particle.displacement_z == + Approx(pod_test->displacement_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.nsize_x == pod_test->nsize_x); + REQUIRE(h5_particle.nsize_y == pod_test->nsize_y); + REQUIRE(h5_particle.nsize_z == pod_test->nsize_z); + + REQUIRE(h5_particle.velocity_x == + Approx(pod_test->velocity_x).epsilon(Tolerance)); + REQUIRE(h5_particle.velocity_y == + Approx(pod_test->velocity_y).epsilon(Tolerance)); + REQUIRE(h5_particle.velocity_z == + Approx(pod_test->velocity_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.stress_xx == + Approx(pod_test->stress_xx).epsilon(Tolerance)); + REQUIRE(h5_particle.stress_yy == + Approx(pod_test->stress_yy).epsilon(Tolerance)); + REQUIRE(h5_particle.stress_zz == + Approx(pod_test->stress_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xy == Approx(pod_test->tau_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_yz == Approx(pod_test->tau_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xz == Approx(pod_test->tau_xz).epsilon(Tolerance)); + + REQUIRE(h5_particle.strain_xx == + Approx(pod_test->strain_xx).epsilon(Tolerance)); + REQUIRE(h5_particle.strain_yy == + Approx(pod_test->strain_yy).epsilon(Tolerance)); + REQUIRE(h5_particle.strain_zz == + Approx(pod_test->strain_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_xy == + Approx(pod_test->gamma_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_yz == + Approx(pod_test->gamma_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_xz == + Approx(pod_test->gamma_xz).epsilon(Tolerance)); + + REQUIRE(h5_particle.epsilon_v == + Approx(pod_test->epsilon_v).epsilon(Tolerance)); + REQUIRE(h5_particle.status == pod_test->status); + REQUIRE(h5_particle.cell_id == pod_test->cell_id); + REQUIRE(h5_particle.material_id == pod_test->material_id); + + REQUIRE(h5_particle.liquid_mass == + Approx(pod_test->liquid_mass).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_x == + Approx(pod_test->liquid_velocity_x).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_y == + Approx(pod_test->liquid_velocity_y).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_z == + Approx(pod_test->liquid_velocity_z).epsilon(Tolerance)); + REQUIRE(h5_particle.porosity == + Approx(pod_test->porosity).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_saturation == + Approx(pod_test->liquid_saturation).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_material_id == + Approx(pod_test->liquid_material_id).epsilon(Tolerance)); + } +} + +//! \brief Check twophase particle class for 2D case +TEST_CASE("TwoPhase Particle is checked for 2D case", + "[particle][2D][2Phase]") { + // Dimension + const unsigned Dim = 2; + // Degree of freedom + const unsigned Dof = 2; + // Number of nodes per cell + const unsigned Nnodes = 4; + // Number of phases + const unsigned Nphases = 2; + // Tolerance + const double Tolerance = 1.E-7; + // Json property + Json jfunctionproperties; + jfunctionproperties["id"] = 0; + std::vector x_values{{0.0, 0.5, 1.0}}; + std::vector fx_values{{0.0, 1.0, 1.0}}; + jfunctionproperties["xvalues"] = x_values; + jfunctionproperties["fxvalues"] = fx_values; + + // math function + std::shared_ptr mfunction = + std::make_shared(0, jfunctionproperties); + // Coordinates + Eigen::Vector2d coords; + coords.setZero(); + + //! Check for id = 0 + SECTION("TwoPhase Particle id is zero") { + mpm::Index id = 0; + auto particle = std::make_shared>(id, coords); + REQUIRE(particle->id() == 0); + } + + SECTION("TwoPhase Particle id is positive") { + //! Check for id is a positive value + mpm::Index id = std::numeric_limits::max(); + auto particle = std::make_shared>(id, coords); + REQUIRE(particle->id() == std::numeric_limits::max()); + } + + //! Test coordinates function + SECTION("coordinates function is checked") { + mpm::Index id = 0; + auto particle = std::make_shared>(id, coords); + + //! Check for coordinates being zero + auto coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + REQUIRE(coordinates.size() == Dim); + + //! Check for negative value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = -1. * std::numeric_limits::max(); + particle->assign_coordinates(coords); + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + + //! Check for positive value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = std::numeric_limits::max(); + particle->assign_coordinates(coords); + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + } + + // Test assign cell to a particle + SECTION("Add a pointer to a cell to particle") { + // Add particle + mpm::Index id = 0; + coords << 0.75, 0.75; + auto particle = std::make_shared>(id, coords); + + // Check particle coordinates + auto coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Element + std::shared_ptr> element = + std::make_shared>(); + + // Create cell + auto cell = std::make_shared>(10, Nnodes, element); + // Add nodes to cell + coords << 0.5, 0.5; + std::shared_ptr> node0 = + std::make_shared>(0, coords); + + coords << 1.5, 0.5; + std::shared_ptr> node1 = + std::make_shared>(1, coords); + + coords << 0.5, 1.5; + std::shared_ptr> node2 = + std::make_shared>(3, coords); + + coords << 1.5, 1.5; + std::shared_ptr> node3 = + std::make_shared>(2, coords); + + coords << 0.5, 3.0; + std::shared_ptr> node4 = + std::make_shared>(3, coords); + + coords << 1.5, 3.0; + std::shared_ptr> node5 = + std::make_shared>(2, coords); + + cell->add_node(0, node0); + cell->add_node(1, node1); + cell->add_node(2, node3); + cell->add_node(3, node2); + REQUIRE(cell->nnodes() == 4); + + // Initialise cell properties + cell->initialise(); + + // Check if cell is initialised + REQUIRE(cell->is_initialised() == true); + + // Add cell to particle + REQUIRE(cell->status() == false); + // Check particle cell status + REQUIRE(particle->cell_ptr() == false); + // Assign cell id + REQUIRE(particle->assign_cell_id(10) == true); + // Require cell id + REQUIRE(particle->cell_id() == 10); + // Assign a very large cell id + REQUIRE(particle->assign_cell_id(std::numeric_limits::max()) == + false); + // Require cell id + REQUIRE(particle->cell_id() == 10); + // Assign particle to cell + REQUIRE(particle->assign_cell(cell) == true); + // Local coordinates + Eigen::Vector2d xi; + xi.fill(std::numeric_limits::max()); + // Assign particle to cell + REQUIRE(particle->assign_cell_xi(cell, xi) == false); + // Compute reference location + cell->is_point_in_cell(particle->coordinates(), &xi); + // Assign particle to cell + REQUIRE(particle->assign_cell_xi(cell, xi) == true); + + // Assign cell id again + REQUIRE(particle->assign_cell_id(10) == false); + // Check particle cell status + REQUIRE(particle->cell_ptr() == true); + // Check cell status on addition of particle + REQUIRE(cell->status() == true); + + // Create cell + auto cell2 = std::make_shared>(20, Nnodes, element); + + cell2->add_node(0, node2); + cell2->add_node(1, node3); + cell2->add_node(2, node5); + cell2->add_node(3, node4); + REQUIRE(cell2->nnodes() == 4); + + // Initialise cell2 properties + cell2->initialise(); + + // Check if cell2 is initialised + REQUIRE(cell2->is_initialised() == true); + + // Add cell2 to particle + REQUIRE(cell2->status() == false); + // Assign particle to cell2 + REQUIRE(particle->assign_cell(cell2) == false); + // Check cell2 status for failed addition of particle + REQUIRE(cell2->status() == false); + // Check cell status because this should not have removed the particle + REQUIRE(cell->status() == true); + + // Remove assigned cell + particle->remove_cell(); + REQUIRE(particle->assign_cell(cell) == true); + + // Clear all particle ids + REQUIRE(cell->nparticles() == 1); + cell->clear_particle_ids(); + REQUIRE(cell->nparticles() == 0); + } + + //! Test initialise particle stresses + SECTION("TwoPhase Particle with initial stress") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + //! Test initialise particle stresses + Eigen::Matrix stress = + Eigen::Matrix::Constant(5.7); + particle->initial_stress(stress); + REQUIRE(particle->stress().size() == stress.size()); + auto pstress = particle->stress(); + for (unsigned i = 0; i < pstress.size(); ++i) + REQUIRE(pstress[i] == Approx(stress[i]).epsilon(Tolerance)); + } + + // !Test initialise particle pore pressure + SECTION("TwoPhase Particle with initial pore pressure") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + bool status = true; + coords << 0.1, 0.2; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + // Assign liquid material + unsigned liquid_mid = 0; + // Initialise material + Json jmaterial_liquid; + jmaterial_liquid["density"] = 1000.; + jmaterial_liquid["bulk_modulus"] = 1.0E+9; + jmaterial_liquid["mu"] = 0.3; + jmaterial_liquid["dynamic_viscosity"] = 0.; + + auto liquid_material = + Factory, unsigned, const Json&>::instance()->create( + "Newtonian2D", std::move(liquid_mid), jmaterial_liquid); + + REQUIRE(particle->assign_material(liquid_material, + mpm::ParticlePhase::Liquid) == true); + + Eigen::Matrix gravity; + gravity << 0., -9.81; + // Test only lefe boundary + std::map reference_points; + reference_points.insert(std::make_pair( + static_cast(0.), static_cast(0.5))); + //! Test initialise pore pressure by water table + REQUIRE(particle->initialise_pore_pressure_watertable(1, 0, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(2943).epsilon(Tolerance)); + + // Test only right boundary + reference_points.erase(0.); + reference_points.insert(std::make_pair( + static_cast(1.), static_cast(0.7))); + //! Test initialise pore pressure by water table + REQUIRE(particle->initialise_pore_pressure_watertable(1, 0, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(4905).epsilon(Tolerance)); + + // Test both left and right boundaries + reference_points.insert(std::make_pair( + static_cast(0.), static_cast(0.5))); + //! Test initialise pore pressure by water table + REQUIRE(particle->initialise_pore_pressure_watertable(1, 0, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(3139.2).epsilon(Tolerance)); + } + + //! Test particles velocity constraints + SECTION("TwoPhase Particle with velocity constraints") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + + // Apply constraints + particle->apply_particle_velocity_constraints(0, 10.5); + particle->apply_particle_velocity_constraints(1, -12.5); + particle->apply_particle_velocity_constraints(2, 20.5); + particle->apply_particle_velocity_constraints(3, -22.5); + + // Check apply constraints + REQUIRE(particle->velocity()(0) == Approx(10.5).epsilon(Tolerance)); + REQUIRE(particle->velocity()(1) == Approx(-12.5).epsilon(Tolerance)); + REQUIRE(particle->liquid_velocity()(0) == Approx(20.5).epsilon(Tolerance)); + REQUIRE(particle->liquid_velocity()(1) == Approx(-22.5).epsilon(Tolerance)); + } + + //! Test particle, cell and node functions + SECTION("Test twophase particle, cell and node functions") { + // Add particle + mpm::Index id = 0; + coords << 0.75, 0.75; + auto particle = std::make_shared>(id, coords); + + // Time-step + const double dt = 0.1; + + // Check particle coordinates + auto coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Shape function + std::shared_ptr> element = + std::make_shared>(); + + // Create cell + auto cell = std::make_shared>(10, Nnodes, element); + // Add nodes to cell + coords << 0.5, 0.5; + std::shared_ptr> node0 = + std::make_shared>(0, coords); + + coords << 1.5, 0.5; + std::shared_ptr> node1 = + std::make_shared>(1, coords); + + coords << 1.5, 1.5; + std::shared_ptr> node2 = + std::make_shared>(2, coords); + + coords << 0.5, 1.5; + std::shared_ptr> node3 = + std::make_shared>(3, coords); + cell->add_node(0, node0); + cell->add_node(1, node1); + cell->add_node(2, node2); + cell->add_node(3, node3); + REQUIRE(cell->nnodes() == 4); + + std::vector>> nodes; + nodes.emplace_back(node0); + nodes.emplace_back(node1); + nodes.emplace_back(node2); + nodes.emplace_back(node3); + + // Initialise cell properties + cell->initialise(); + + // Add cell to particle + REQUIRE(cell->status() == false); + // Check compute shape functions of a particle + // TODO Assert: REQUIRE_NOTHROW(particle->compute_shapefn()); + // Compute reference location should throw + REQUIRE(particle->compute_reference_location() == false); + // Compute updated particle location should fail + // TODO Assert: + // REQUIRE_NOTHROW(particle->compute_updated_position(dt) == false); + // Compute updated particle location from nodal velocity should fail + // TODO Assert: REQUIRE_NOTHROW(particle->compute_updated_position(dt, + // true)); Compute volume + // TODO Assert: REQUIRE(particle->compute_volume() == false); + // Update volume should fail + // TODO Assert: REQUIRE(particle->update_volume() == false); + + REQUIRE(particle->assign_cell(cell) == true); + REQUIRE(cell->status() == true); + REQUIRE(particle->cell_id() == 10); + + // Check if cell is initialised + REQUIRE(cell->is_initialised() == true); + + // Check compute shape functions of a particle + REQUIRE_NOTHROW(particle->compute_shapefn()); + + // Assign volume + REQUIRE(particle->assign_volume(0.0) == false); + REQUIRE(particle->assign_volume(-5.0) == false); + REQUIRE(particle->assign_volume(2.0) == true); + // Check volume + REQUIRE(particle->volume() == Approx(2.0).epsilon(Tolerance)); + // Compute volume + REQUIRE_NOTHROW(particle->compute_volume()); + // Check volume + REQUIRE(particle->volume() == Approx(1.0).epsilon(Tolerance)); + + // Check reference location + coords << -0.5, -0.5; + REQUIRE(particle->compute_reference_location() == true); + auto ref_coordinates = particle->reference_location(); + for (unsigned i = 0; i < ref_coordinates.size(); ++i) + REQUIRE(ref_coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Assign material + unsigned mid = 1; + // Initialise material + Json jmaterial; + jmaterial["density"] = 1000.; + jmaterial["youngs_modulus"] = 1.0E+7; + jmaterial["poisson_ratio"] = 0.3; + jmaterial["porosity"] = 0.3; + jmaterial["k_x"] = 0.001; + jmaterial["k_y"] = 0.001; + + auto material = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic2D", std::move(mid), jmaterial); + + // Assign liquid material + unsigned liquid_mid = 2; + // Initialise material + Json jmaterial_liquid; + jmaterial_liquid["density"] = 1000.; + jmaterial_liquid["bulk_modulus"] = 1.0E+9; + jmaterial_liquid["mu"] = 0.3; + jmaterial_liquid["dynamic_viscosity"] = 0.; + + auto liquid_material = + Factory, unsigned, const Json&>::instance()->create( + "Newtonian2D", std::move(liquid_mid), jmaterial_liquid); + + // Check compute mass before material and volume + // TODO Assert: REQUIRE(particle->compute_mass() == false); + + // Test compute stress before material assignment + // TODO Assert: REQUIRE(particle->compute_stress() == false); + + // Initialise nodal variables + for (unsigned i = 0; i < nodes.size(); ++i) + REQUIRE_NOTHROW(nodes.at(i)->initialise_twophase()); + + // Assign material properties + REQUIRE(particle->assign_material(material) == true); + REQUIRE(particle->assign_material(liquid_material, + mpm::ParticlePhase::Liquid) == true); + + // Assign porosity + REQUIRE(particle->assign_porosity() == true); + + // Assign permeability + REQUIRE(particle->assign_permeability() == true); + + // Check material id + REQUIRE(particle->material_id() == 1); + REQUIRE(particle->material_id(mpm::ParticlePhase::Liquid) == 2); + + // Compute volume + REQUIRE_NOTHROW(particle->compute_volume()); + + // Compute mass + REQUIRE_NOTHROW(particle->compute_mass()); + // Mass + REQUIRE(particle->mass() == Approx(700.).epsilon(Tolerance)); + REQUIRE(particle->liquid_mass() == Approx(300.).epsilon(Tolerance)); + + // Map particle mass to nodes + particle->assign_mass(std::numeric_limits::max()); + // TODO Assert: REQUIRE_NOTHROW(particle->map_mass_momentum_to_nodes()); + + // Map particle pressure to nodes + // TODO Assert: REQUIRE(particle->map_pressure_to_nodes() == false); + + // Assign mass to nodes + REQUIRE(particle->compute_reference_location() == true); + REQUIRE_NOTHROW(particle->compute_shapefn()); + + // Check velocity + Eigen::VectorXd velocity; + velocity.resize(Dim); + for (unsigned i = 0; i < velocity.size(); ++i) velocity(i) = i; + REQUIRE(particle->assign_velocity(velocity) == true); + REQUIRE(particle->assign_liquid_velocity(velocity) == true); + for (unsigned i = 0; i < velocity.size(); ++i) { + REQUIRE(particle->velocity()(i) == Approx(i).epsilon(Tolerance)); + REQUIRE(particle->liquid_velocity()(i) == Approx(i).epsilon(Tolerance)); + } + + REQUIRE_NOTHROW(particle->compute_mass()); + REQUIRE_NOTHROW(particle->map_mass_momentum_to_nodes()); + + // TODO Assert: REQUIRE(particle->map_pressure_to_nodes() == false); + REQUIRE(particle->compute_pressure_smoothing() == false); + + // Values of nodal mass + std::array nodal_mass{562.5, 187.5, 62.5, 187.5}; + // Check nodal mass + for (unsigned i = 0; i < nodes.size(); ++i) { + // Solid phase + REQUIRE(nodes.at(i)->mass(mpm::NodePhase::NSolid) == + Approx(nodal_mass.at(i) * (1 - particle->porosity())) + .epsilon(Tolerance)); + // Liquid phase + REQUIRE( + nodes.at(i)->mass(mpm::NodePhase::NLiquid) == + Approx(nodal_mass.at(i) * particle->porosity()).epsilon(Tolerance)); + } + + // Compute nodal velocity + for (const auto& node : nodes) node->compute_velocity(); + + // Values of nodal momentum + Eigen::Matrix nodal_momentum; + // clang-format off + nodal_momentum << 0., 562.5, + 0., 187.5, + 0., 62.5, + 0., 187.5; + // clang-format on + // Check nodal momentum + for (unsigned i = 0; i < nodal_momentum.rows(); ++i) + for (unsigned j = 0; j < nodal_momentum.cols(); ++j) { + // Solid phase + REQUIRE(nodes.at(i)->momentum(mpm::NodePhase::NSolid)(j) == + Approx(nodal_momentum(i, j) * (1 - particle->porosity())) + .epsilon(Tolerance)); + // Liquid phase + REQUIRE(nodes.at(i)->momentum(mpm::NodePhase::NLiquid)(j) == + Approx(nodal_momentum(i, j) * particle->porosity()) + .epsilon(Tolerance)); + } + // Values of nodal velocity + Eigen::Matrix nodal_velocity; + // clang-format off + nodal_velocity << 0., 1., + 0., 1., + 0., 1., + 0., 1.; + // clang-format on + // Check nodal velocity + for (unsigned i = 0; i < nodal_velocity.rows(); ++i) + for (unsigned j = 0; j < nodal_velocity.cols(); ++j) { + // Solid phase + REQUIRE(nodes.at(i)->velocity(mpm::NodePhase::NSolid)(j) == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + // Liquid phase + REQUIRE(nodes.at(i)->velocity(mpm::NodePhase::NLiquid)(j) == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + } + + // Set momentum to get non-zero strain + // clang-format off + nodal_momentum << 0., 562.5 * 1., + 0., 187.5 * 2., + 0., 62.5 * 3., + 0., 187.5 * 4.; + // clang-format on + for (unsigned i = 0; i < nodes.size(); ++i) { + // Solid phase + REQUIRE_NOTHROW(nodes.at(i)->update_momentum( + false, mpm::NodePhase::NSolid, + nodal_momentum.row(i) * (1 - particle->porosity()))); + // Liquid phase + REQUIRE_NOTHROW(nodes.at(i)->update_momentum( + false, mpm::NodePhase::NLiquid, + nodal_momentum.row(i) * particle->porosity())); + } + + // nodal velocity + // clang-format off + nodal_velocity << 0., 1., + 0., 2., + 0., 3., + 0., 4.; + // clang-format on + // Compute nodal velocity + for (const auto& node : nodes) node->compute_velocity(); + // Check nodal velocity + for (unsigned i = 0; i < nodal_velocity.rows(); ++i) + for (unsigned j = 0; j < nodal_velocity.cols(); ++j) { + // Solid phase + REQUIRE(nodes.at(i)->velocity(mpm::NodePhase::NSolid)(j) == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + // Liquid phase + REQUIRE(nodes.at(i)->velocity(mpm::NodePhase::NLiquid)(j) == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + } + + // Check pressure + REQUIRE(std::isnan(particle->pressure()) == true); + + // Compute strain + particle->compute_strain(dt); + // Strain + Eigen::Matrix strain; + strain << 0., 0.25, 0., 0.050, 0., 0.; + // Check strains + for (unsigned i = 0; i < strain.rows(); ++i) + REQUIRE(particle->strain()(i) == Approx(strain(i)).epsilon(Tolerance)); + + // Check volumetric strain at centroid + double volumetric_strain = 0.2; + REQUIRE(particle->volumetric_strain_centroid() == + Approx(volumetric_strain).epsilon(Tolerance)); + + // Check updated pressure + const double K = 8333333.333333333; + REQUIRE(std::isnan(particle->pressure()) == true); + + // Update volume strain rate + REQUIRE(particle->volume() == Approx(1.0).epsilon(Tolerance)); + particle->compute_strain(dt); + REQUIRE_NOTHROW(particle->update_volume()); + REQUIRE(particle->volume() == Approx(1.2).epsilon(Tolerance)); + + // Compute stress + REQUIRE_NOTHROW(particle->compute_stress()); + + Eigen::Matrix stress; + // clang-format off + stress << 721153.8461538460 * 2., + 1682692.3076923075 * 2., + 721153.8461538460 * 2., + 96153.8461538462 * 2., + 0.0000000000 * 2., + 0.0000000000 * 2.; + // clang-format on + // Check stress + for (unsigned i = 0; i < stress.rows(); ++i) + REQUIRE(particle->stress()(i) == Approx(stress(i)).epsilon(Tolerance)); + + // Compute pore_pressure + REQUIRE_NOTHROW(particle->compute_pore_pressure(dt)); + // Check pore pressure + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(-666666666.6666667461).epsilon(Tolerance)); + + // Check body force + Eigen::Matrix gravity; + gravity << 0., -9.81; + + particle->map_body_force(gravity); + + // Body force + Eigen::Matrix body_force; + // clang-format off + body_force << 0., -5518.125, + 0., -1839.375, + 0., -613.125, + 0., -1839.375; + // clang-format on + + // Check nodal body force + for (unsigned i = 0; i < body_force.rows(); ++i) + for (unsigned j = 0; j < body_force.cols(); ++j) + REQUIRE(nodes[i]->external_force(mpm::NodePhase::NSolid)[j] == + Approx(body_force(i, j)).epsilon(Tolerance)); + + // Check traction force + double traction = 7.68; + const unsigned direction = 1; + // Assign volume + REQUIRE(particle->assign_volume(0.0) == false); + REQUIRE(particle->assign_volume(-5.0) == false); + REQUIRE(particle->assign_volume(2.0) == true); + // Map traction force + double current_time = 5.0; + // Assign traction to particle + particle->assign_traction(direction, + mfunction->value(current_time) * traction); + particle->map_traction_force(); + + // Traction force + Eigen::Matrix traction_force; + // shapefn * volume / size_(dir) * traction + // clang-format off + traction_force << 0., 0.5625 * 1.414213562 * 7.68, + 0., 0.1875 * 1.414213562 * 7.68, + 0., 0.0625 * 1.414213562 * 7.68, + 0., 0.1875 * 1.414213562 * 7.68; + // clang-format on + // Add previous external body force + traction_force += body_force; + + // Check nodal traction force + for (unsigned i = 0; i < traction_force.rows(); ++i) + for (unsigned j = 0; j < traction_force.cols(); ++j) + REQUIRE(nodes[i]->external_force(mpm::NodePhase::NSolid)[j] == + Approx(traction_force(i, j)).epsilon(Tolerance)); + // Reset traction + particle->assign_traction(direction, + -traction * mfunction->value(current_time)); + // Map traction force + particle->map_traction_force(); + // Check nodal external force + for (unsigned i = 0; i < traction_force.rows(); ++i) + for (unsigned j = 0; j < traction_force.cols(); ++j) + REQUIRE(nodes[i]->external_force(mpm::NodePhase::NSolid)[j] == + Approx(body_force(i, j)).epsilon(Tolerance)); + + // Internal force + Eigen::Matrix internal_force; + // clang-format off + internal_force << 501225961.538461626, 502668269.230769277, + -501033653.846153915, 167363782.051282048, -167075320.512820542, + -167556089.743589759, 166883012.820512831, -502475961.538461566; + // clang-format on + + // Map particle internal force + particle->assign_volume(1.0); + particle->map_internal_force(); + + // Check nodal internal force + for (unsigned i = 0; i < internal_force.rows(); ++i) + for (unsigned j = 0; j < internal_force.cols(); ++j) + REQUIRE(nodes[i]->internal_force(mpm::NodePhase::NMixture)[j] == + Approx(internal_force(i, j)).epsilon(Tolerance)); + + // Internal force + Eigen::Matrix drag_force_coefficient; + // clang-format off + drag_force_coefficient << 496631.25, 496631.25, + 165543.75, 165543.75, + 55181.25, 55181.25, + 165543.75, 165543.75; + + // Map drag force coefficient + particle->map_drag_force_coefficient(); + + // Check nodal drag force coefficient + for (unsigned i = 0; i < drag_force_coefficient.rows(); ++i) + for (unsigned j = 0; j < drag_force_coefficient.cols(); ++j) + REQUIRE(nodes[i]->drag_force_coefficient()[j] == + Approx(drag_force_coefficient(i, j)).epsilon(Tolerance)); + + // Calculate nodal acceleration and velocity + for (const auto& node : nodes) + node->compute_acceleration_velocity_twophase_explicit(dt); + + // Check nodal velocity + Eigen::Matrix nodal_liquid_velocity; + // clang-format off + nodal_velocity << 89200.2442002442258, 89566.563566544588, + -267454.212454212538, 89421.0434200244199, -267600.732600732648, + -268697.614699633734, 89053.7240537240723, -268550.094553113624; + nodal_liquid_velocity << 88888.8888888888905, 88888.9078888889053, + -266666.666666666686, 88889.9078888889053, -266666.666666666686, + -266664.647666666715, 88888.8888888889051, -266663.647666666657; + // clang-format on + // Check nodal velocity + for (unsigned i = 0; i < nodal_velocity.rows(); ++i) + for (unsigned j = 0; j < nodal_velocity.cols(); ++j) { + // Solid phase + REQUIRE(nodes[i]->velocity(mpm::NodePhase::NSolid)[j] == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + // Liquid phase + REQUIRE(nodes[i]->velocity(mpm::NodePhase::NLiquid)[j] == + Approx(nodal_liquid_velocity(i, j)).epsilon(Tolerance)); + } + + // Check nodal acceleration + Eigen::Matrix nodal_acceleration; + Eigen::Matrix nodal_liquid_acceleration; + // clang-format off + nodal_acceleration << 892002.4420024422, 895655.635665445821, + -2674542.12454212504, 894190.434200244141, -2676007.3260073266, + -2687006.14699633745, 890537.240537240636, -2685540.94553113589; + nodal_liquid_acceleration << 888888.888888888876, 888879.078888888936, + -2666666.66666666651, 888879.078888889053, -2666666.66666666698, + -2666676.47666666703, 888888.888888888992, -2666676.47666666657; + // clang-format on + // Check nodal acceleration + for (unsigned i = 0; i < nodal_acceleration.rows(); ++i) + for (unsigned j = 0; j < nodal_acceleration.cols(); ++j) { + // Solid phase + REQUIRE(nodes[i]->acceleration(mpm::NodePhase::NSolid)[j] == + Approx(nodal_acceleration(i, j)).epsilon(Tolerance)); + // Liquid phase + REQUIRE(nodes[i]->acceleration(mpm::NodePhase::NLiquid)[j] == + Approx(nodal_liquid_acceleration(i, j)).epsilon(Tolerance)); + } + // Approx(nodal_velocity(i, j) / dt).epsilon(Tolerance)); + + // Check original particle coordinates + coords << 0.75, 0.75; + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Compute updated particle location + REQUIRE_NOTHROW(particle->compute_updated_position(dt)); + // Check particle velocity + velocity << 0., 0.019; + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == + Approx(velocity(i)).epsilon(Tolerance)); + + // Check particle displacement + Eigen::Vector2d displacement; + displacement << 0., 0.0894; + for (unsigned i = 0; i < displacement.size(); ++i) + REQUIRE(particle->displacement()(i) == + Approx(displacement(i)).epsilon(Tolerance)); + + // Updated particle coordinate + coords << 0.75, .8394; + // Check particle coordinates + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Compute updated particle location from nodal velocity + REQUIRE_NOTHROW(particle->compute_updated_position(dt, true)); + // Check particle velocity + velocity << 0., 0.894; + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == + Approx(velocity(i)).epsilon(Tolerance)); + + // Check particle displacement + displacement << 0., 0.1788; + for (unsigned i = 0; i < displacement.size(); ++i) + REQUIRE(particle->displacement()(i) == + Approx(displacement(i)).epsilon(Tolerance)); + + // Updated particle coordinate + coords << 0.75, .9288; + // Check particle coordinates + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Update porosity + REQUIRE_NOTHROW(particle->update_porosity(dt)); + + // Check porosity + REQUIRE(particle->porosity() == Approx(0.44).epsilon(Tolerance)); + + SECTION("TwoPhase Particle assign state variables") { + SECTION("Assign state variable fail") { + mid = 0; + Json jmaterial; + jmaterial["density"] = 1000.; + jmaterial["youngs_modulus"] = 1.0E+7; + jmaterial["poisson_ratio"] = 0.3; + jmaterial["softening"] = false; + jmaterial["friction"] = 0.; + jmaterial["dilation"] = 0.; + jmaterial["cohesion"] = 2000.; + jmaterial["residual_friction"] = 0.; + jmaterial["residual_dilation"] = 0.; + jmaterial["residual_cohesion"] = 1000.; + jmaterial["peak_pdstrain"] = 0.; + jmaterial["residual_pdstrain"] = 0.; + jmaterial["tension_cutoff"] = 0.; + + auto mc_material = + Factory, unsigned, const Json&>::instance() + ->create("MohrCoulomb2D", std::move(id), jmaterial); + REQUIRE(mc_material->id() == 0); + + mpm::dense_map state_variables = + mc_material->initialise_state_variables(); + REQUIRE(state_variables.at("phi") == + Approx(jmaterial["friction"]).epsilon(Tolerance)); + REQUIRE(state_variables.at("psi") == + Approx(jmaterial["dilation"]).epsilon(Tolerance)); + REQUIRE(state_variables.at("cohesion") == + Approx(jmaterial["cohesion"]).epsilon(Tolerance)); + REQUIRE(state_variables.at("epsilon") == Approx(0.).epsilon(Tolerance)); + REQUIRE(state_variables.at("rho") == Approx(0.).epsilon(Tolerance)); + REQUIRE(state_variables.at("theta") == Approx(0.).epsilon(Tolerance)); + REQUIRE(state_variables.at("pdstrain") == + Approx(0.).epsilon(Tolerance)); + + SECTION("Assign state variables") { + // Assign material properties + REQUIRE(particle->assign_material(mc_material) == true); + // Assign state variables + REQUIRE(particle->assign_material_state_vars(state_variables, + mc_material) == true); + // Assign and read a state variable + REQUIRE_NOTHROW(particle->assign_state_variable("phi", 30.)); + REQUIRE(particle->state_variable("phi") == 30.); + // Assign and read pressure though MC does not contain pressure + REQUIRE_NOTHROW( + particle->assign_pressure(30., mpm::ParticlePhase::Liquid)); + REQUIRE(std::isnan(particle->pressure()) == true); + } + + SECTION("Assign state variables fail on state variables size") { + // Assign material + unsigned mid1 = 0; + // Initialise material + Json jmaterial1; + jmaterial1["density"] = 1000.; + jmaterial1["bulk_modulus"] = 8333333.333333333; + jmaterial1["dynamic_viscosity"] = 8.9E-4; + + auto newtonian_material = + Factory, unsigned, const Json&>::instance() + ->create("Newtonian2D", std::move(mid1), jmaterial1); + + // Assign material properties + REQUIRE(particle->assign_material(newtonian_material) == true); + // Assign state variables + REQUIRE(particle->assign_material_state_vars(state_variables, + mc_material) == false); + } + + SECTION("Assign state variables fail on material id") { + // Assign material + unsigned mid1 = 1; + // Initialise material + Json jmaterial1; + jmaterial1["density"] = 1000.; + jmaterial1["bulk_modulus"] = 8333333.333333333; + jmaterial1["dynamic_viscosity"] = 8.9E-4; + + auto newtonian_material = + Factory, unsigned, const Json&>::instance() + ->create("Newtonian2D", std::move(mid1), jmaterial1); + + // Assign material properties + REQUIRE(particle->assign_material(newtonian_material) == true); + // Assign state variables + REQUIRE(particle->assign_material_state_vars(state_variables, + mc_material) == false); + } + } + } + } + + SECTION("Check assign material to particle") { + // Add particle + mpm::Index id = 0; + coords << 0.75, 0.75; + auto particle = std::make_shared>(id, coords); + + unsigned mid = 1; + // Initialise material + Json jmaterial; + jmaterial["density"] = 1000.; + jmaterial["youngs_modulus"] = 1.0E+7; + jmaterial["poisson_ratio"] = 0.3; + + auto material = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic2D", std::move(mid), jmaterial); + REQUIRE(material->id() == 1); + + // Check if particle can be assigned a material is null + REQUIRE(particle->assign_material(nullptr) == false); + + // Check material id + REQUIRE(particle->material_id() == std::numeric_limits::max()); + + // Assign material to particle + REQUIRE(particle->assign_material(material) == true); + + // Check material id + REQUIRE(particle->material_id() == 1); + } + + SECTION("Check twophase particle properties") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> particle = + std::make_shared>(id, coords); + + // Check mass + REQUIRE(particle->mass() == Approx(0.0).epsilon(Tolerance)); + double mass = 100.5; + particle->assign_mass(mass); + REQUIRE(particle->mass() == Approx(100.5).epsilon(Tolerance)); + + // Check stress + Eigen::Matrix stress; + for (unsigned i = 0; i < stress.size(); ++i) stress(i) = 17.52; + + for (unsigned i = 0; i < stress.size(); ++i) + REQUIRE(particle->stress()(i) == Approx(0.).epsilon(Tolerance)); + + // Check velocity + Eigen::VectorXd velocity; + velocity.resize(Dim); + for (unsigned i = 0; i < velocity.size(); ++i) velocity(i) = 19.745; + + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == Approx(0.).epsilon(Tolerance)); + + REQUIRE(particle->assign_velocity(velocity) == true); + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == Approx(19.745).epsilon(Tolerance)); + + // Assign volume + REQUIRE(particle->assign_volume(0.0) == false); + REQUIRE(particle->assign_volume(-5.0) == false); + REQUIRE(particle->assign_volume(2.0) == true); + // Check volume + REQUIRE(particle->volume() == Approx(2.0).epsilon(Tolerance)); + // Traction + double traction = 65.32; + const unsigned Direction = 1; + // Check traction + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + + REQUIRE(particle->assign_traction(Direction, traction) == true); + + // Calculate traction force = traction * volume / spacing + traction *= 2.0 / (std::pow(2.0, 1. / Dim)); + + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(particle->traction()(i) == Approx(traction).epsilon(Tolerance)); + else + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + } + + // Check for incorrect direction + const unsigned wrong_dir = 4; + REQUIRE(particle->assign_traction(wrong_dir, traction) == false); + + // Check again to ensure value hasn't been updated + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(particle->traction()(i) == Approx(traction).epsilon(Tolerance)); + else + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + } + } + + // Check initialise particle from POD file + SECTION("Check initialise particle POD") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> particle = + std::make_shared>(id, coords); + + mpm::PODParticleTwoPhase h5_particle; + h5_particle.id = 13; + h5_particle.mass = 501.5; + + Eigen::Vector3d coords; + coords << 1., 2., 0.; + h5_particle.coord_x = coords[0]; + h5_particle.coord_y = coords[1]; + h5_particle.coord_z = coords[2]; + + Eigen::Vector3d displacement; + displacement << 0.01, 0.02, 0.0; + h5_particle.displacement_x = displacement[0]; + h5_particle.displacement_y = displacement[1]; + h5_particle.displacement_z = displacement[2]; + + Eigen::Vector3d lsize; + lsize << 0.25, 0.5, 0.; + h5_particle.nsize_x = lsize[0]; + h5_particle.nsize_y = lsize[1]; + h5_particle.nsize_z = lsize[2]; + + Eigen::Vector3d velocity; + velocity << 1.5, 2.5, 0.0; + h5_particle.velocity_x = velocity[0]; + h5_particle.velocity_y = velocity[1]; + h5_particle.velocity_z = velocity[2]; + + Eigen::Matrix stress; + stress << 11.5, -12.5, 13.5, 14.5, -15.5, 16.5; + h5_particle.stress_xx = stress[0]; + h5_particle.stress_yy = stress[1]; + h5_particle.stress_zz = stress[2]; + h5_particle.tau_xy = stress[3]; + h5_particle.tau_yz = stress[4]; + h5_particle.tau_xz = stress[5]; + + Eigen::Matrix strain; + strain << 0.115, -0.125, 0.135, 0.145, -0.155, 0.165; + h5_particle.strain_xx = strain[0]; + h5_particle.strain_yy = strain[1]; + h5_particle.strain_zz = strain[2]; + h5_particle.gamma_xy = strain[3]; + h5_particle.gamma_yz = strain[4]; + h5_particle.gamma_xz = strain[5]; + + h5_particle.epsilon_v = strain.head(Dim).sum(); + + h5_particle.status = true; + + h5_particle.cell_id = 1; + + h5_particle.volume = 2.; + + h5_particle.material_id = 1; + + h5_particle.liquid_mass = 100.1; + + Eigen::Vector3d liquid_velocity; + liquid_velocity << 5.5, 2.1, 0.; + h5_particle.liquid_velocity_x = liquid_velocity[0]; + h5_particle.liquid_velocity_y = liquid_velocity[1]; + h5_particle.liquid_velocity_z = liquid_velocity[2]; + + h5_particle.porosity = 0.33; + + h5_particle.liquid_saturation = 1.; + + h5_particle.liquid_material_id = 2; + + // Reinitialise particle from POD data + REQUIRE(particle->initialise_particle(h5_particle) == true); + + // Check particle id + REQUIRE(particle->id() == h5_particle.id); + // Check particle mass + REQUIRE(particle->mass() == h5_particle.mass); + // Check particle volume + REQUIRE(particle->volume() == h5_particle.volume); + // Check particle mass density + REQUIRE(particle->mass_density() == h5_particle.mass / h5_particle.volume); + // Check particle status + REQUIRE(particle->status() == h5_particle.status); + + // Check for coordinates + auto coordinates = particle->coordinates(); + REQUIRE(coordinates.size() == Dim); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Check for displacement + auto pdisplacement = particle->displacement(); + REQUIRE(pdisplacement.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pdisplacement(i) == Approx(displacement(i)).epsilon(Tolerance)); + + // Check for size + auto size = particle->natural_size(); + REQUIRE(size.size() == Dim); + for (unsigned i = 0; i < size.size(); ++i) + REQUIRE(size(i) == Approx(lsize(i)).epsilon(Tolerance)); + + // Check velocity + auto pvelocity = particle->velocity(); + REQUIRE(pvelocity.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pvelocity(i) == Approx(velocity(i)).epsilon(Tolerance)); + + // Check stress + auto pstress = particle->stress(); + REQUIRE(pstress.size() == stress.size()); + for (unsigned i = 0; i < stress.size(); ++i) + REQUIRE(pstress(i) == Approx(stress(i)).epsilon(Tolerance)); + + // Check strain + auto pstrain = particle->strain(); + REQUIRE(pstrain.size() == strain.size()); + for (unsigned i = 0; i < strain.size(); ++i) + REQUIRE(pstrain(i) == Approx(strain(i)).epsilon(Tolerance)); + + // Check particle volumetric strain centroid + REQUIRE(particle->volumetric_strain_centroid() == h5_particle.epsilon_v); + + // Check cell id + REQUIRE(particle->cell_id() == h5_particle.cell_id); + + // Check material id + REQUIRE(particle->material_id() == h5_particle.material_id); + + // Check liquid mass + REQUIRE(particle->liquid_mass() == h5_particle.liquid_mass); + + // Check liquid velocity + auto pliquid_velocity = particle->liquid_velocity(); + REQUIRE(pliquid_velocity.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pliquid_velocity(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + + // Check porosity + REQUIRE(particle->porosity() == h5_particle.porosity); + + // Check liquid material id + REQUIRE(particle->material_id(mpm::ParticlePhase::Liquid) == + h5_particle.liquid_material_id); + + // Write Particle POD data + auto pod_test = + std::static_pointer_cast(particle->pod()); + + REQUIRE(h5_particle.id == pod_test->id); + REQUIRE(h5_particle.mass == pod_test->mass); + + REQUIRE(h5_particle.coord_x == + Approx(pod_test->coord_x).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_y == + Approx(pod_test->coord_y).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_z == + Approx(pod_test->coord_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.displacement_x == + Approx(pod_test->displacement_x).epsilon(Tolerance)); + REQUIRE(h5_particle.displacement_y == + Approx(pod_test->displacement_y).epsilon(Tolerance)); + REQUIRE(h5_particle.displacement_z == + Approx(pod_test->displacement_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.nsize_x == pod_test->nsize_x); + REQUIRE(h5_particle.nsize_y == pod_test->nsize_y); + REQUIRE(h5_particle.nsize_z == pod_test->nsize_z); + + REQUIRE(h5_particle.velocity_x == + Approx(pod_test->velocity_x).epsilon(Tolerance)); + REQUIRE(h5_particle.velocity_y == + Approx(pod_test->velocity_y).epsilon(Tolerance)); + REQUIRE(h5_particle.velocity_z == + Approx(pod_test->velocity_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.stress_xx == + Approx(pod_test->stress_xx).epsilon(Tolerance)); + REQUIRE(h5_particle.stress_yy == + Approx(pod_test->stress_yy).epsilon(Tolerance)); + REQUIRE(h5_particle.stress_zz == + Approx(pod_test->stress_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xy == Approx(pod_test->tau_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_yz == Approx(pod_test->tau_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xz == Approx(pod_test->tau_xz).epsilon(Tolerance)); + + REQUIRE(h5_particle.strain_xx == + Approx(pod_test->strain_xx).epsilon(Tolerance)); + REQUIRE(h5_particle.strain_yy == + Approx(pod_test->strain_yy).epsilon(Tolerance)); + REQUIRE(h5_particle.strain_zz == + Approx(pod_test->strain_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_xy == + Approx(pod_test->gamma_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_yz == + Approx(pod_test->gamma_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_xz == + Approx(pod_test->gamma_xz).epsilon(Tolerance)); + + REQUIRE(h5_particle.epsilon_v == + Approx(pod_test->epsilon_v).epsilon(Tolerance)); + REQUIRE(h5_particle.status == pod_test->status); + REQUIRE(h5_particle.cell_id == pod_test->cell_id); + REQUIRE(h5_particle.material_id == pod_test->material_id); + + REQUIRE(h5_particle.liquid_mass == + Approx(pod_test->liquid_mass).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_x == + Approx(pod_test->liquid_velocity_x).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_y == + Approx(pod_test->liquid_velocity_y).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_z == + Approx(pod_test->liquid_velocity_z).epsilon(Tolerance)); + REQUIRE(h5_particle.porosity == + Approx(pod_test->porosity).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_saturation == + Approx(pod_test->liquid_saturation).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_material_id == + Approx(pod_test->liquid_material_id).epsilon(Tolerance)); + } + + // Check twophase particle's material id maping to nodes + SECTION("Check twophase particle's material id maping to nodes") { + // Add particle + mpm::Index id1 = 0; + coords << 0.75, 0.75; + auto particle1 = std::make_shared>(id1, coords); + + // Add particle + mpm::Index id2 = 1; + coords << 0.25, 0.25; + auto particle2 = std::make_shared>(id2, coords); + + // Element + std::shared_ptr> element = + std::make_shared>(); + + // Create cell + auto cell = std::make_shared>(10, Nnodes, element); + // Create vector of nodes and add them to cell + coords << 0., 0.; + std::shared_ptr> node0 = + std::make_shared>(0, coords); + + coords << 1., 0.; + std::shared_ptr> node1 = + std::make_shared>(1, coords); + + coords << 1., 1.; + std::shared_ptr> node2 = + std::make_shared>(3, coords); + + coords << 0., 1.; + std::shared_ptr> node3 = + std::make_shared>(2, coords); + std::vector>> nodes = {node0, node1, + node2, node3}; + + for (int j = 0; j < nodes.size(); ++j) cell->add_node(j, nodes[j]); + + // Initialise cell properties and assign cell to particle + cell->initialise(); + particle1->assign_cell(cell); + particle2->assign_cell(cell); + + // Assign material 1 + unsigned mid1 = 0; + // Initialise material 1 + Json jmaterial1; + jmaterial1["density"] = 1000.; + jmaterial1["youngs_modulus"] = 1.0E+7; + jmaterial1["poisson_ratio"] = 0.3; + + auto material1 = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic2D", std::move(mid1), jmaterial1); + + particle1->assign_material(material1); + + // Assign material 2 + unsigned mid2 = 1; + // Initialise material 2 + Json jmaterial2; + jmaterial2["density"] = 2000.; + jmaterial2["youngs_modulus"] = 2.0E+7; + jmaterial2["poisson_ratio"] = 0.25; + + auto material2 = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic2D", std::move(mid2), jmaterial2); + + particle2->assign_material(material2); + + // Append particle's material id to nodes in cell + particle1->append_material_id_to_nodes(); + particle2->append_material_id_to_nodes(); + + // check if the correct amount of material ids were added to node and if + // their indexes are correct + std::vector material_ids = {0, 1}; + for (const auto& node : nodes) { + REQUIRE(node->material_ids().size() == 2); + auto mat_ids = node->material_ids(); + unsigned i = 0; + for (auto mitr = mat_ids.begin(); mitr != mat_ids.end(); ++mitr, ++i) + REQUIRE(*mitr == material_ids.at(i)); + } + } +} + +//! \brief Check twophase particle class for 3D case +TEST_CASE("TwoPhase Particle is checked for 3D case", + "[particle][3D][2Phase]") { + // Dimension + const unsigned Dim = 3; + // Dimension + const unsigned Dof = 6; + // Number of nodes per cell + const unsigned Nnodes = 8; + // Number of phases + const unsigned Nphases = 2; + // Tolerance + const double Tolerance = 1.E-7; + // Json property + Json jfunctionproperties; + jfunctionproperties["id"] = 0; + std::vector x_values{{0.0, 0.5, 1.0}}; + std::vector fx_values{{0.0, 1.0, 1.0}}; + jfunctionproperties["xvalues"] = x_values; + jfunctionproperties["fxvalues"] = fx_values; + + // math function + std::shared_ptr mfunction = + std::make_shared(0, jfunctionproperties); + // Current time for traction force + double current_time = 10.0; + + // Coordinates + Eigen::Vector3d coords; + coords.setZero(); + + //! Check for id = 0 + SECTION("TwoPhase Particle id is zero") { + mpm::Index id = 0; + std::shared_ptr> particle = + std::make_shared>(id, coords); + REQUIRE(particle->id() == 0); + REQUIRE(particle->status() == true); + } + + SECTION("TwoPhase Particle id is positive") { + //! Check for id is a positive value + mpm::Index id = std::numeric_limits::max(); + std::shared_ptr> particle = + std::make_shared>(id, coords); + REQUIRE(particle->id() == std::numeric_limits::max()); + REQUIRE(particle->status() == true); + } + + //! Construct with id, coordinates and status + SECTION("TwoPhase Particle with id, coordinates, and status") { + mpm::Index id = 0; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + REQUIRE(particle->id() == 0); + REQUIRE(particle->status() == true); + particle->assign_status(false); + REQUIRE(particle->status() == false); + } + + //! Test coordinates function + SECTION("coordinates function is checked") { + mpm::Index id = 0; + // Create particle + std::shared_ptr> particle = + std::make_shared>(id, coords); + + //! Check for coordinates being zero + auto coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + REQUIRE(coordinates.size() == Dim); + + //! Check for negative value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = -1. * std::numeric_limits::max(); + particle->assign_coordinates(coords); + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + + //! Check for positive value of coordinates + for (unsigned i = 0; i < coordinates.size(); ++i) + coords(i) = std::numeric_limits::max(); + particle->assign_coordinates(coords); + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + REQUIRE(coordinates.size() == Dim); + } + + //! Test assign cell pointer to particle + SECTION("Add a pointer to a cell to particle") { + // Add particle + mpm::Index id = 0; + coords << 1.5, 1.5, 1.5; + std::shared_ptr> particle = + std::make_shared>(id, coords); + + // Check particle coordinates + auto coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Assign hexahedron shape function + std::shared_ptr> element = + std::make_shared>(); + + // Create cell + auto cell = std::make_shared>(10, Nnodes, element); + // Add nodes + coords << 0, 0, 0; + std::shared_ptr> node0 = + std::make_shared>(0, coords); + + coords << 2, 0, 0; + std::shared_ptr> node1 = + std::make_shared>(1, coords); + + coords << 2, 2, 0; + std::shared_ptr> node2 = + std::make_shared>(2, coords); + + coords << 0, 2, 0; + std::shared_ptr> node3 = + std::make_shared>(3, coords); + + coords << 0, 0, 2; + std::shared_ptr> node4 = + std::make_shared>(4, coords); + + coords << 2, 0, 2; + std::shared_ptr> node5 = + std::make_shared>(5, coords); + + coords << 2, 2, 2; + std::shared_ptr> node6 = + std::make_shared>(6, coords); + + coords << 0, 2, 2; + std::shared_ptr> node7 = + std::make_shared>(7, coords); + + coords << 0, 0, 4; + std::shared_ptr> node8 = + std::make_shared>(4, coords); + + coords << 2, 0, 4; + std::shared_ptr> node9 = + std::make_shared>(5, coords); + + coords << 2, 2, 4; + std::shared_ptr> node10 = + std::make_shared>(6, coords); + + coords << 0, 2, 4; + std::shared_ptr> node11 = + std::make_shared>(7, coords); + + cell->add_node(0, node0); + cell->add_node(1, node1); + cell->add_node(2, node2); + cell->add_node(3, node3); + cell->add_node(4, node4); + cell->add_node(5, node5); + cell->add_node(6, node6); + cell->add_node(7, node7); + REQUIRE(cell->nnodes() == 8); + + // Initialise cell properties + cell->initialise(); + + // Check if cell is initialised + REQUIRE(cell->is_initialised() == true); + + // Add cell to particle + REQUIRE(cell->status() == false); + // Check particle cell status + REQUIRE(particle->cell_ptr() == false); + // Assign cell id + REQUIRE(particle->assign_cell_id(10) == true); + // Require cell id + REQUIRE(particle->cell_id() == 10); + // Assign a very large cell id + REQUIRE(particle->assign_cell_id(std::numeric_limits::max()) == + false); + // Require cell id + REQUIRE(particle->cell_id() == 10); + // Assign particle to cell + REQUIRE(particle->assign_cell(cell) == true); + // Local coordinates + Eigen::Vector3d xi; + xi.fill(std::numeric_limits::max()); + // Assign particle to cell + REQUIRE(particle->assign_cell_xi(cell, xi) == false); + // Compute reference location + cell->is_point_in_cell(particle->coordinates(), &xi); + // Assign particle to cell + REQUIRE(particle->assign_cell_xi(cell, xi) == true); + + // Assign cell id again + REQUIRE(particle->assign_cell_id(10) == false); + // Check particle cell status + REQUIRE(particle->cell_ptr() == true); + // Check cell status on addition of particle + REQUIRE(cell->status() == true); + + // Create cell + auto cell2 = std::make_shared>(20, Nnodes, element); + + cell2->add_node(0, node4); + cell2->add_node(1, node5); + cell2->add_node(2, node6); + cell2->add_node(3, node7); + cell2->add_node(4, node8); + cell2->add_node(5, node9); + cell2->add_node(6, node10); + cell2->add_node(7, node11); + REQUIRE(cell2->nnodes() == 8); + + // Initialise cell2 properties + cell2->initialise(); + + // Check if cell2 is initialised + REQUIRE(cell2->is_initialised() == true); + + // Add cell2 to particle + REQUIRE(cell2->status() == false); + // Assign particle to cell2 + REQUIRE(particle->assign_cell(cell2) == false); + // Check cell2 status for failed addition of particle + REQUIRE(cell2->status() == false); + // Check cell status because this should not have removed the particle + REQUIRE(cell->status() == true); + + // Remove assigned cell + particle->remove_cell(); + REQUIRE(particle->assign_cell(cell) == true); + + // Clear all particle ids + REQUIRE(cell->nparticles() == 1); + cell->clear_particle_ids(); + REQUIRE(cell->nparticles() == 0); + } + + //! Test initialise particle stresses + SECTION("TwoPhase Particle with initial stress") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + //! Test initialise particle stresses + Eigen::Matrix stress = + Eigen::Matrix::Constant(5.7); + particle->initial_stress(stress); + REQUIRE(particle->stress().size() == stress.size()); + auto pstress = particle->stress(); + for (unsigned i = 0; i < pstress.size(); ++i) + REQUIRE(pstress[i] == Approx(stress[i]).epsilon(Tolerance)); + } + + // !Test initialise particle pore pressure + SECTION("TwoPhase Particle with initial pore pressure") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + bool status = true; + coords << 0.1, 0.2, 0.3; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + // Assign liquid material + unsigned liquid_mid = 0; + // Initialise material + Json jmaterial_liquid; + jmaterial_liquid["density"] = 1000.; + jmaterial_liquid["bulk_modulus"] = 1.0E+9; + jmaterial_liquid["mu"] = 0.3; + jmaterial_liquid["dynamic_viscosity"] = 0.; + + auto liquid_material = + Factory, unsigned, const Json&>::instance()->create( + "Newtonian3D", std::move(liquid_mid), jmaterial_liquid); + + REQUIRE(particle->assign_material(liquid_material, + mpm::ParticlePhase::Liquid) == true); + + Eigen::Matrix gravity; + gravity << 0., -9.81, 0; + // Test only lefe boundary + std::map reference_points; + reference_points.insert(std::make_pair( + static_cast(0.), static_cast(0.5))); + //! Test initialise pore pressure by water table with x-interpolation + REQUIRE(particle->initialise_pore_pressure_watertable(1, 0, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(2943).epsilon(Tolerance)); + //! Test initialise pore pressure by water table with z-interpolation + REQUIRE(particle->initialise_pore_pressure_watertable(1, 2, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(2943).epsilon(Tolerance)); + + // Test only right boundary + reference_points.erase(0.); + reference_points.insert(std::make_pair( + static_cast(1.), static_cast(0.7))); + //! Test initialise pore pressure by water table x-interpolation + REQUIRE(particle->initialise_pore_pressure_watertable(1, 0, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(4905).epsilon(Tolerance)); + //! Test initialise pore pressure by water table with z-interpolation + REQUIRE(particle->initialise_pore_pressure_watertable(1, 2, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(4905).epsilon(Tolerance)); + + // Test both left and right boundaries + reference_points.insert(std::make_pair( + static_cast(0.), static_cast(0.5))); + //! Test initialise pore pressure by water table x-interpolation + REQUIRE(particle->initialise_pore_pressure_watertable(1, 0, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(3139.2).epsilon(Tolerance)); + //! Test initialise pore pressure by water table with z-interpolation + REQUIRE(particle->initialise_pore_pressure_watertable(1, 2, gravity, + reference_points)); + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(3531.6).epsilon(Tolerance)); + } + + //! Test twophase particles velocity constraints + SECTION("TwoPhase Particle with velocity constraints") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + bool status = true; + std::shared_ptr> particle = + std::make_shared>(id, coords, status); + // Apply constraints + particle->apply_particle_velocity_constraints(0, 10.5); + particle->apply_particle_velocity_constraints(1, -12.5); + particle->apply_particle_velocity_constraints(2, 14.5); + particle->apply_particle_velocity_constraints(3, 20.5); + particle->apply_particle_velocity_constraints(4, -22.5); + particle->apply_particle_velocity_constraints(5, 24.5); + + // Check apply constraints + REQUIRE(particle->velocity()(0) == Approx(10.5).epsilon(Tolerance)); + REQUIRE(particle->velocity()(1) == Approx(-12.5).epsilon(Tolerance)); + REQUIRE(particle->velocity()(2) == Approx(14.5).epsilon(Tolerance)); + REQUIRE(particle->liquid_velocity()(0) == Approx(20.5).epsilon(Tolerance)); + REQUIRE(particle->liquid_velocity()(1) == Approx(-22.5).epsilon(Tolerance)); + REQUIRE(particle->liquid_velocity()(2) == Approx(24.5).epsilon(Tolerance)); + } + + //! Test particle, cell and node functions + SECTION("Test particle, cell and node functions") { + // Add particle + mpm::Index id = 0; + coords << 1.5, 1.5, 1.5; + std::shared_ptr> particle = + std::make_shared>(id, coords); + + // Time-step + const double dt = 0.1; + + // Check particle coordinates + auto coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Assign hexahedron shape function + std::shared_ptr> element = + std::make_shared>(); + + // Create cell + auto cell = std::make_shared>(10, Nnodes, element); + // Add nodes + coords << 0, 0, 0; + std::shared_ptr> node0 = + std::make_shared>(0, coords); + + coords << 2, 0, 0; + std::shared_ptr> node1 = + std::make_shared>(1, coords); + + coords << 2, 2, 0; + std::shared_ptr> node2 = + std::make_shared>(2, coords); + + coords << 0, 2, 0; + std::shared_ptr> node3 = + std::make_shared>(3, coords); + + coords << 0, 0, 2; + std::shared_ptr> node4 = + std::make_shared>(4, coords); + + coords << 2, 0, 2; + std::shared_ptr> node5 = + std::make_shared>(5, coords); + + coords << 2, 2, 2; + std::shared_ptr> node6 = + std::make_shared>(6, coords); + + coords << 0, 2, 2; + std::shared_ptr> node7 = + std::make_shared>(7, coords); + + std::vector>> nodes; + nodes.emplace_back(node0); + nodes.emplace_back(node1); + nodes.emplace_back(node2); + nodes.emplace_back(node3); + nodes.emplace_back(node4); + nodes.emplace_back(node5); + nodes.emplace_back(node6); + nodes.emplace_back(node7); + + cell->add_node(0, node0); + cell->add_node(1, node1); + cell->add_node(2, node2); + cell->add_node(3, node3); + cell->add_node(4, node4); + cell->add_node(5, node5); + cell->add_node(6, node6); + cell->add_node(7, node7); + REQUIRE(cell->nnodes() == 8); + + // Initialise cell properties + cell->initialise(); + + // Check if cell is initialised + REQUIRE(cell->is_initialised() == true); + + // Add cell to particle + REQUIRE(cell->status() == false); + // Check compute shape functions of a particle + // TODO Assert: REQUIRE(particle->compute_shapefn() == false); + // Compute reference location should throw + REQUIRE(particle->compute_reference_location() == false); + // Compute updated particle location should fail + // TODO Assert: REQUIRE(particle->compute_updated_position(dt) == false); + // Compute updated particle location from nodal velocity should fail + // TODO Assert: REQUIRE_NOTHROW(particle->compute_updated_position(dt, + // true)); + + // Compute volume + // TODO Assert: REQUIRE(particle->compute_volume() == false); + // Update volume should fail + // TODO Assert: REQUIRE(particle->update_volume() == false); + + REQUIRE(particle->assign_cell(cell) == true); + REQUIRE(cell->status() == true); + REQUIRE(particle->cell_id() == 10); + + // Check if cell is initialised + REQUIRE(cell->is_initialised() == true); + + // Check compute shape functions of a particle + REQUIRE_NOTHROW(particle->compute_shapefn()); + + // Assign volume + REQUIRE(particle->assign_volume(0.0) == false); + REQUIRE(particle->assign_volume(-5.0) == false); + REQUIRE(particle->assign_volume(2.0) == true); + // Check volume + REQUIRE(particle->volume() == Approx(2.0).epsilon(Tolerance)); + // Compute volume + REQUIRE_NOTHROW(particle->compute_volume()); + // Check volume + REQUIRE(particle->volume() == Approx(8.0).epsilon(Tolerance)); + + // Check reference location + coords << 0.5, 0.5, 0.5; + REQUIRE(particle->compute_reference_location() == true); + auto ref_coordinates = particle->reference_location(); + for (unsigned i = 0; i < ref_coordinates.size(); ++i) + REQUIRE(ref_coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Assign material + unsigned mid = 0; + // Initialise material + Json jmaterial; + jmaterial["density"] = 1000.; + jmaterial["youngs_modulus"] = 1.0E+7; + jmaterial["poisson_ratio"] = 0.3; + jmaterial["porosity"] = 0.3; + jmaterial["k_x"] = 0.001; + jmaterial["k_y"] = 0.001; + jmaterial["k_z"] = 0.001; + + auto material = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic3D", std::move(mid), jmaterial); + + // Assign liquid material + unsigned liquid_mid = 1; + // Initialise material + Json jmaterial_liquid; + jmaterial_liquid["density"] = 1000.; + jmaterial_liquid["bulk_modulus"] = 1.0E+9; + jmaterial_liquid["mu"] = 0.3; + jmaterial_liquid["dynamic_viscosity"] = 0.; + + auto liquid_material = + Factory, unsigned, const Json&>::instance()->create( + "Newtonian3D", std::move(liquid_mid), jmaterial_liquid); + + // Check compute mass before material and volume + // TODO Assert: REQUIRE(particle->compute_mass() == false); + + // Test compute stress before material assignment + // TODO Assert: REQUIRE(particle->compute_stress() == false); + + // Assign material properties + REQUIRE(particle->assign_material(material) == true); + REQUIRE(particle->assign_material(liquid_material, + mpm::ParticlePhase::Liquid) == true); + + // Check material id from particle + REQUIRE(particle->material_id() == 0); + REQUIRE(particle->material_id(mpm::ParticlePhase::Liquid) == 1); + + // Assign porosity + REQUIRE(particle->assign_porosity() == true); + + // Assign permeability + REQUIRE(particle->assign_permeability() == true); + + // Compute volume + REQUIRE_NOTHROW(particle->compute_volume()); + + // Compute mass + REQUIRE_NOTHROW(particle->compute_mass()); + // Mass + REQUIRE(particle->mass() == Approx(5600.).epsilon(Tolerance)); + REQUIRE(particle->liquid_mass() == Approx(2400.).epsilon(Tolerance)); + + // Map particle mass to nodes + particle->assign_mass(std::numeric_limits::max()); + // TODO Assert: REQUIRE(particle->map_mass_momentum_to_nodes() == false); + + // Map particle pressure to nodes + // TODO Assert: REQUIRE(particle->map_pressure_to_nodes() == false); + + // Assign mass to nodes + REQUIRE(particle->compute_reference_location() == true); + REQUIRE_NOTHROW(particle->compute_shapefn()); + + // Check velocity + Eigen::VectorXd velocity; + velocity.resize(Dim); + for (unsigned i = 0; i < velocity.size(); ++i) velocity(i) = i; + REQUIRE(particle->assign_velocity(velocity) == true); + REQUIRE(particle->assign_liquid_velocity(velocity) == true); + for (unsigned i = 0; i < velocity.size(); ++i) { + REQUIRE(particle->velocity()(i) == Approx(i).epsilon(Tolerance)); + REQUIRE(particle->liquid_velocity()(i) == Approx(i).epsilon(Tolerance)); + } + + REQUIRE_NOTHROW(particle->compute_mass()); + REQUIRE_NOTHROW(particle->map_mass_momentum_to_nodes()); + + // TODO Assert: REQUIRE(particle->map_pressure_to_nodes() == false); + REQUIRE(particle->compute_pressure_smoothing() == false); + + // Values of nodal mass + std::array nodal_mass{125., 375., 1125., 375., + 375., 1125., 3375., 1125.}; + // Check nodal mass + for (unsigned i = 0; i < nodes.size(); ++i) { + // Solid phase + REQUIRE(nodes.at(i)->mass(mpm::NodePhase::NSolid) == + Approx(nodal_mass.at(i) * (1 - particle->porosity()))); + // Liquid phase + REQUIRE( + nodes.at(i)->mass(mpm::NodePhase::NLiquid) == + Approx(nodal_mass.at(i) * particle->porosity()).epsilon(Tolerance)); + } + + // Compute nodal velocity + for (const auto& node : nodes) node->compute_velocity(); + + // Values of nodal momentum + Eigen::Matrix nodal_momentum; + // clang-format off + nodal_momentum << 0., 125., 250., + 0., 375., 750., + 0., 1125., 2250., + 0., 375., 750., + 0., 375., 750., + 0., 1125., 2250., + 0., 3375., 6750., + 0., 1125., 2250.; + // clang-format on + + // Check nodal momentum + for (unsigned i = 0; i < nodal_momentum.rows(); ++i) + for (unsigned j = 0; j < nodal_momentum.cols(); ++j) { + // Solid phase + REQUIRE(nodes.at(i)->momentum(mpm::NodePhase::NSolid)(j) == + Approx(nodal_momentum(i, j) * (1 - particle->porosity())) + .epsilon(Tolerance)); + // Liquid phase + REQUIRE(nodes.at(i)->momentum(mpm::NodePhase::NLiquid)(j) == + Approx(nodal_momentum(i, j) * particle->porosity()) + .epsilon(Tolerance)); + } + + // Values of nodal velocity + Eigen::Matrix nodal_velocity; + // clang-format off + nodal_velocity << 0., 1., 2., + 0., 1., 2., + 0., 1., 2., + 0., 1., 2., + 0., 1., 2., + 0., 1., 2., + 0., 1., 2., + 0., 1., 2.; + // clang-format on + // Check nodal velocity + for (unsigned i = 0; i < nodal_velocity.rows(); ++i) + for (unsigned j = 0; j < nodal_velocity.cols(); ++j) { + // Solid phase + REQUIRE(nodes.at(i)->velocity(mpm::NodePhase::NSolid)(j) == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + // Liquid phase + REQUIRE(nodes.at(i)->velocity(mpm::NodePhase::NLiquid)(j) == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + } + + // Set momentum to get non-zero strain + // clang-format off + nodal_momentum << 0., 125. * 1., 250. * 1., + 0., 375. * 2., 750. * 2., + 0., 1125. * 3., 2250. * 3., + 0., 375. * 4., 750. * 4., + 0., 375. * 5., 750. * 5., + 0., 1125. * 6., 2250. * 6., + 0., 3375. * 7., 6750. * 7., + 0., 1125. * 8., 2250. * 8.; + // clang-format on + for (unsigned i = 0; i < nodes.size(); ++i) { + // Solid phase + REQUIRE_NOTHROW(nodes.at(i)->update_momentum( + false, mpm::NodePhase::NSolid, + nodal_momentum.row(i) * (1 - particle->porosity()))); + // Liquid phase + REQUIRE_NOTHROW(nodes.at(i)->update_momentum( + false, mpm::NodePhase::NLiquid, + nodal_momentum.row(i) * particle->porosity())); + } + + // nodal velocity + // clang-format off + nodal_velocity << 0., 1., 2., + 0., 2., 4., + 0., 3., 6., + 0., 4., 8., + 0., 5., 10., + 0., 6., 12., + 0., 7., 14., + 0., 8., 16.; + // clang-format on + // Compute nodal velocity + for (const auto& node : nodes) node->compute_velocity(); + // Check nodal velocity + for (unsigned i = 0; i < nodal_velocity.rows(); ++i) + for (unsigned j = 0; j < nodal_velocity.cols(); ++j) { + // Solid phase + REQUIRE(nodes.at(i)->velocity(mpm::NodePhase::NSolid)(j) == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + // Liquid phase + REQUIRE(nodes.at(i)->velocity(mpm::NodePhase::NLiquid)(j) == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + } + + // Check pressure + REQUIRE(std::isnan(particle->pressure()) == true); + + // Compute strain + particle->compute_strain(dt); + // Strain + Eigen::Matrix strain; + strain << 0.00000, 0.07500, 0.40000, -0.02500, 0.35000, -0.05000; + + // Check strains + for (unsigned i = 0; i < strain.rows(); ++i) + REQUIRE(particle->strain()(i) == Approx(strain(i)).epsilon(Tolerance)); + + // Check volumetric strain at centroid + double volumetric_strain = 0.5; + REQUIRE(particle->volumetric_strain_centroid() == + Approx(volumetric_strain).epsilon(Tolerance)); + + // Check updated pressure + REQUIRE(std::isnan(particle->pressure()) == true); + + // Update volume strain rate + REQUIRE(particle->volume() == Approx(8.0).epsilon(Tolerance)); + particle->compute_strain(dt); + REQUIRE_NOTHROW(particle->update_volume()); + REQUIRE(particle->volume() == Approx(12.0).epsilon(Tolerance)); + + // Compute stress + REQUIRE_NOTHROW(particle->compute_stress()); + + Eigen::Matrix stress; + // clang-format off + stress << 2740384.6153846150, + 3317307.6923076920, + 5817307.6923076920, + -96153.8461538463, + 1346153.8461538465, + -192307.6923076927; + // clang-format on + // Check stress + for (unsigned i = 0; i < stress.rows(); ++i) + REQUIRE(particle->stress()(i) == Approx(stress(i)).epsilon(Tolerance)); + + // Compute pore_pressure + REQUIRE_NOTHROW(particle->compute_pore_pressure(dt)); + // Check pore pressure + REQUIRE(particle->pressure(mpm::ParticlePhase::Liquid) == + Approx(-1666666666.6666669846).epsilon(Tolerance)); + + // Check body force + Eigen::Matrix gravity; + gravity << 0., 0., -9.81; + + particle->map_body_force(gravity); + + // Body force + Eigen::Matrix body_force; + // clang-format off + body_force << 0., 0., -1226.25, + 0., 0., -3678.75, + 0., 0., -11036.25, + 0., 0., -3678.75, + 0., 0., -3678.75, + 0., 0., -11036.25, + 0., 0., -33108.75, + 0., 0., -11036.25; + // clang-format on + + // Check nodal body force + for (unsigned i = 0; i < body_force.rows(); ++i) + for (unsigned j = 0; j < body_force.cols(); ++j) + REQUIRE(nodes[i]->external_force(mpm::NodePhase::NSolid)[j] == + Approx(body_force(i, j)).epsilon(Tolerance)); + + // Check traction force + double traction = 7.68; + const unsigned direction = 2; + // Assign volume + REQUIRE(particle->assign_volume(0.0) == false); + REQUIRE(particle->assign_volume(-5.0) == false); + REQUIRE(particle->assign_volume(2.0) == true); + // Assign traction to particle + particle->assign_traction(direction, + mfunction->value(current_time) * traction); + // Map traction force + particle->map_traction_force(); + + // Traction force + Eigen::Matrix traction_force; + // shapefn * volume / size_(dir) * traction + // clang-format off + traction_force << 0., 0., 0.015625 * 1.587401052 * 7.68, + 0., 0., 0.046875 * 1.587401052 * 7.68, + 0., 0., 0.140625 * 1.587401052 * 7.68, + 0., 0., 0.046875 * 1.587401052 * 7.68, + 0., 0., 0.046875 * 1.587401052 * 7.68, + 0., 0., 0.140625 * 1.587401052 * 7.68, + 0., 0., 0.421875 * 1.587401052 * 7.68, + 0., 0., 0.140625 * 1.587401052 * 7.68; + // clang-format on + // Add previous external body force + traction_force += body_force; + + // Check nodal traction force + for (unsigned i = 0; i < traction_force.rows(); ++i) + for (unsigned j = 0; j < traction_force.cols(); ++j) + REQUIRE(nodes[i]->external_force(mpm::NodePhase::NSolid)[j] == + Approx(traction_force(i, j)).epsilon(Tolerance)); + // Reset traction + particle->assign_traction(direction, + mfunction->value(current_time) * -traction); + // Map traction force + particle->map_traction_force(); + // Check nodal external force + for (unsigned i = 0; i < traction_force.rows(); ++i) + for (unsigned j = 0; j < traction_force.cols(); ++j) + REQUIRE(nodes[i]->external_force(mpm::NodePhase::NSolid)[j] == + Approx(body_force(i, j)).epsilon(Tolerance)); + + // Internal force + Eigen::Matrix internal_force; + // clang-format off + internal_force << 417279647.435897529, 417808493.589743674, + 418409455.12820518, -417568108.974359035, 1253521634.61538506, + 1255420673.07692337, -1252415865.38461566, -1249387019.2307694, + 3762223557.69230843, 1251935096.1538465, -416558493.589743674, + 1253882211.53846169, 1252031250.00000024, 1252079326.92307711, + -417255608.974359095, -1252127403.84615421, 3756526442.307693, + -1251189903.84615397, -3755516826.92307711, -3760276442.307693, + -3765685096.15384674, 3756382211.53846216, -1253713942.30769277, + -1255805288.46153879; + // clang-format on + + // Map particle internal force + particle->assign_volume(8.0); + particle->map_internal_force(); + + // Check nodal internal force + for (unsigned i = 0; i < internal_force.rows(); ++i) + for (unsigned j = 0; j < internal_force.cols(); ++j) + REQUIRE(nodes[i]->internal_force(mpm::NodePhase::NMixture)[j] == + Approx(internal_force(i, j)).epsilon(Tolerance)); + + // Calculate nodal acceleration and velocity + for (const auto& node : nodes) + node->compute_acceleration_velocity(mpm::NodePhase::NSolid, dt); + + // Check nodal velocity + // clang-format off + nodal_velocity << 476891.025641025801, 477496.421245421399, + 478182.833003663225, -159073.565323565388, 477534.051282051601, + 478258.093076923338, -159036.935286935361, -158649.319902319956, + 477747.272564102721, 476927.655677655945, -158684.949938950012, + 477676.012490842724, 476964.285714285914, 476987.600732600898, + -158945.919133089221, -159000.305250305333, 477025.230769230926, + -158870.659059829108, -158963.675213675248, -159158.140415140486, + -159381.479572649638, 477000.915750915941, -159193.770451770542, + -159452.739645909722; + // clang-format on + // Check nodal velocity + for (unsigned i = 0; i < nodal_velocity.rows(); ++i) + for (unsigned j = 0; j < nodal_velocity.cols(); ++j) + REQUIRE(nodes[i]->velocity(mpm::NodePhase::NSolid)[j] == + Approx(nodal_velocity(i, j)).epsilon(Tolerance)); + + // Check nodal acceleration + Eigen::Matrix nodal_acceleration; + // clang-format off + nodal_acceleration << 4768910.25641025789, 4774954.21245421376, + 4781808.33003663179, -1590735.65323565388, 4775320.51282051578, + 4782540.93076923303, -1590369.35286935349, -1586523.1990231995, + 4777412.72564102709, 4769276.55677655898, -1586889.49938950012, + 4776680.12490842678, 4769642.85714285914, 4769826.00732600875, + -1589559.19133089203, -1590003.0525030531, 4770192.30769230891, + -1588826.59059829102, -1589636.75213675248, -1591651.40415140474, + -1593954.79572649626, 4770009.1575091593, -1592017.70451770537, + -1594687.39645909704; + // clang-format on + // Check nodal acceleration + for (unsigned i = 0; i < nodal_acceleration.rows(); ++i) + for (unsigned j = 0; j < nodal_acceleration.cols(); ++j) + REQUIRE(nodes[i]->acceleration(mpm::NodePhase::NSolid)[j] == + Approx(nodal_acceleration(i, j)).epsilon(Tolerance)); + + // Approx(nodal_velocity(i, j) / dt).epsilon(Tolerance)); + + // Check original particle coordinates + coords << 1.5, 1.5, 1.5; + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + SECTION("Particle pressure smoothing") { + // Assign material + unsigned mid1 = 0; + // Initialise material + Json jmaterial1; + jmaterial1["density"] = 1000.; + jmaterial1["bulk_modulus"] = 8333333.333333333; + jmaterial1["dynamic_viscosity"] = 8.9E-4; + + auto material1 = + Factory, unsigned, const Json&>::instance() + ->create("Newtonian3D", std::move(mid1), jmaterial1); + + // Assign material properties + REQUIRE(particle->assign_material(material1) == true); + + // Compute volume + REQUIRE_NOTHROW(particle->compute_volume()); + + // Compute mass + REQUIRE_NOTHROW(particle->compute_mass()); + // Mass + REQUIRE(particle->mass() == Approx(5600.).epsilon(Tolerance)); + REQUIRE(particle->liquid_mass() == Approx(2400.).epsilon(Tolerance)); + + // Map particle mass to nodes + particle->assign_mass(std::numeric_limits::max()); + // TODO Assert: REQUIRE(particle->map_mass_momentum_to_nodes() == + // false); + + // Map particle pressure to nodes + // TODO Assert: REQUIRE(particle->map_pressure_to_nodes() == false); + + // Assign mass to nodes + REQUIRE(particle->compute_reference_location() == true); + REQUIRE_NOTHROW(particle->compute_shapefn()); + + // Check velocity + velocity.resize(Dim); + for (unsigned i = 0; i < velocity.size(); ++i) velocity(i) = i; + REQUIRE(particle->assign_velocity(velocity) == true); + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == Approx(i).epsilon(Tolerance)); + + REQUIRE_NOTHROW(particle->compute_mass()); + REQUIRE_NOTHROW(particle->map_mass_momentum_to_nodes()); + + // Check volumetric strain at centroid + volumetric_strain = 0.5; + REQUIRE(particle->dvolumetric_strain() == + Approx(volumetric_strain).epsilon(Tolerance)); + + // Compute stress + REQUIRE_NOTHROW(particle->compute_stress()); + + REQUIRE( + particle->pressure() == + Approx(-8333333.333333333 * volumetric_strain).epsilon(Tolerance)); + + REQUIRE_NOTHROW(particle->map_pressure_to_nodes()); + REQUIRE(particle->compute_pressure_smoothing() == true); + } + + SECTION("Particle assign state variables") { + SECTION("Assign state variable fail") { + mid = 0; + Json jmaterial; + jmaterial["density"] = 1000.; + jmaterial["youngs_modulus"] = 1.0E+7; + jmaterial["poisson_ratio"] = 0.3; + jmaterial["softening"] = false; + jmaterial["friction"] = 0.; + jmaterial["dilation"] = 0.; + jmaterial["cohesion"] = 2000.; + jmaterial["residual_friction"] = 0.; + jmaterial["residual_dilation"] = 0.; + jmaterial["residual_cohesion"] = 1000.; + jmaterial["peak_pdstrain"] = 0.; + jmaterial["residual_pdstrain"] = 0.; + jmaterial["tension_cutoff"] = 0.; + + auto mc_material = + Factory, unsigned, const Json&>::instance() + ->create("MohrCoulomb3D", std::move(id), jmaterial); + REQUIRE(mc_material->id() == 0); + + mpm::dense_map state_variables = + mc_material->initialise_state_variables(); + REQUIRE(state_variables.at("phi") == + Approx(jmaterial["friction"]).epsilon(Tolerance)); + REQUIRE(state_variables.at("psi") == + Approx(jmaterial["dilation"]).epsilon(Tolerance)); + REQUIRE(state_variables.at("cohesion") == + Approx(jmaterial["cohesion"]).epsilon(Tolerance)); + REQUIRE(state_variables.at("epsilon") == Approx(0.).epsilon(Tolerance)); + REQUIRE(state_variables.at("rho") == Approx(0.).epsilon(Tolerance)); + REQUIRE(state_variables.at("theta") == Approx(0.).epsilon(Tolerance)); + REQUIRE(state_variables.at("pdstrain") == + Approx(0.).epsilon(Tolerance)); + + SECTION("Assign state variables") { + // Assign material properties + REQUIRE(particle->assign_material(mc_material) == true); + // Assign state variables + REQUIRE(particle->assign_material_state_vars(state_variables, + mc_material) == true); + // Assign and read a state variable + REQUIRE_NOTHROW(particle->assign_state_variable("phi", 30.)); + REQUIRE(particle->state_variable("phi") == 30.); + // Assign and read pressure though MC does not contain pressure + REQUIRE_NOTHROW( + particle->assign_pressure(30., mpm::ParticlePhase::Liquid)); + REQUIRE(std::isnan(particle->pressure()) == true); + } + + SECTION("Assign state variables fail on state variables size") { + // Assign material + unsigned mid1 = 0; + // Initialise material + Json jmaterial1; + jmaterial1["density"] = 1000.; + jmaterial1["bulk_modulus"] = 8333333.333333333; + jmaterial1["dynamic_viscosity"] = 8.9E-4; + + auto newtonian_material = + Factory, unsigned, const Json&>::instance() + ->create("Newtonian3D", std::move(mid1), jmaterial1); + + // Assign material properties + REQUIRE(particle->assign_material(newtonian_material) == true); + // Assign state variables + REQUIRE(particle->assign_material_state_vars(state_variables, + mc_material) == false); + } + + SECTION("Assign state variables fail on material id") { + // Assign material + unsigned mid1 = 1; + // Initialise material + Json jmaterial1; + jmaterial1["density"] = 1000.; + jmaterial1["bulk_modulus"] = 8333333.333333333; + jmaterial1["dynamic_viscosity"] = 8.9E-4; + + auto newtonian_material = + Factory, unsigned, const Json&>::instance() + ->create("Newtonian3D", std::move(mid1), jmaterial1); + + // Assign material properties + REQUIRE(particle->assign_material(newtonian_material) == true); + // Assign state variables + REQUIRE(particle->assign_material_state_vars(state_variables, + mc_material) == false); + } + } + } + + // Compute updated particle location + REQUIRE_NOTHROW(particle->compute_updated_position(dt)); + // Check particle velocity + velocity << 0., 1., 0.5985714286; + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == + Approx(velocity(i)).epsilon(Tolerance)); + + // Check particle displacement + Eigen::Vector3d displacement; + displacement << 0.0, 0.5875, 1.0348571429; + for (unsigned i = 0; i < displacement.size(); ++i) + REQUIRE(particle->displacement()(i) == + Approx(displacement(i)).epsilon(Tolerance)); + + // Updated particle coordinate + coords << 1.5, 2.0875, 2.5348571429; + // Check particle coordinates + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + + // Compute updated particle location based on nodal velocity + REQUIRE_NOTHROW(particle->compute_updated_position(dt, true)); + // Check particle velocity + velocity << 0., 5.875, 10.3485714286; + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == + Approx(velocity(i)).epsilon(Tolerance)); + + // Check particle displacement + displacement << 0.0, 1.175, 2.0697142857; + for (unsigned i = 0; i < displacement.size(); ++i) + REQUIRE(particle->displacement()(i) == + Approx(displacement(i)).epsilon(Tolerance)); + + // Updated particle coordinate + coords << 1.5, 2.675, 3.5697142857; + // Check particle coordinates + coordinates = particle->coordinates(); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + } + + SECTION("Check assign material to particle") { + // Add particle + mpm::Index id = 0; + coords << 0.75, 0.75, 0.75; + auto particle = std::make_shared>(id, coords); + + unsigned mid = 1; + // Initialise material + Json jmaterial; + jmaterial["density"] = 1000.; + jmaterial["youngs_modulus"] = 1.0E+7; + jmaterial["poisson_ratio"] = 0.3; + + auto material = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic3D", std::move(mid), jmaterial); + REQUIRE(material->id() == 1); + + // Check if particle can be assigned a null material + REQUIRE(particle->assign_material(nullptr) == false); + // Check material id + REQUIRE(particle->material_id() == std::numeric_limits::max()); + + // Assign material to particle + REQUIRE(particle->assign_material(material) == true); + // Check material id + REQUIRE(particle->material_id() == 1); + } + + SECTION("Check particle properties") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> particle = + std::make_shared>(id, coords); + + // Check mass + REQUIRE(particle->mass() == Approx(0.0).epsilon(Tolerance)); + double mass = 100.5; + particle->assign_mass(mass); + REQUIRE(particle->mass() == Approx(100.5).epsilon(Tolerance)); + + // Check stress + Eigen::Matrix stress; + for (unsigned i = 0; i < stress.size(); ++i) stress(i) = 1.; + + for (unsigned i = 0; i < stress.size(); ++i) + REQUIRE(particle->stress()(i) == Approx(0.).epsilon(Tolerance)); + + // Check velocity + Eigen::VectorXd velocity; + velocity.resize(Dim); + for (unsigned i = 0; i < velocity.size(); ++i) velocity(i) = 17.51; + + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == Approx(0.).epsilon(Tolerance)); + + REQUIRE(particle->assign_velocity(velocity) == true); + for (unsigned i = 0; i < velocity.size(); ++i) + REQUIRE(particle->velocity()(i) == Approx(17.51).epsilon(Tolerance)); + + // Assign volume + REQUIRE(particle->assign_volume(0.0) == false); + REQUIRE(particle->assign_volume(-5.0) == false); + REQUIRE(particle->assign_volume(2.0) == true); + // Check volume + REQUIRE(particle->volume() == Approx(2.0).epsilon(Tolerance)); + // Traction + double traction = 65.32; + const unsigned Direction = 1; + // Check traction + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + + REQUIRE(particle->assign_traction(Direction, traction) == true); + + // Calculate traction force = traction * volume / spacing + traction *= 2.0 / (std::pow(2.0, 1. / Dim)); + + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(particle->traction()(i) == Approx(traction).epsilon(Tolerance)); + else + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + } + + // Check for incorrect direction + const unsigned wrong_dir = 6; + REQUIRE(particle->assign_traction(wrong_dir, traction) == false); + + // Check again to ensure value hasn't been updated + for (unsigned i = 0; i < Dim; ++i) { + if (i == Direction) + REQUIRE(particle->traction()(i) == Approx(traction).epsilon(Tolerance)); + else + REQUIRE(particle->traction()(i) == Approx(0.).epsilon(Tolerance)); + } + } + + // Check initialise particle from POD file + SECTION("Check initialise particle POD") { + mpm::Index id = 0; + const double Tolerance = 1.E-7; + std::shared_ptr> particle = + std::make_shared>(id, coords); + + mpm::PODParticleTwoPhase h5_particle; + h5_particle.id = 13; + h5_particle.mass = 501.5; + + Eigen::Vector3d coords; + coords << 1., 2., 3.; + h5_particle.coord_x = coords[0]; + h5_particle.coord_y = coords[1]; + h5_particle.coord_z = coords[2]; + + Eigen::Vector3d displacement; + displacement << 0.01, 0.02, 0.03; + h5_particle.displacement_x = displacement[0]; + h5_particle.displacement_y = displacement[1]; + h5_particle.displacement_z = displacement[2]; + + Eigen::Vector3d lsize; + lsize << 0.25, 0.5, 0.75; + h5_particle.nsize_x = lsize[0]; + h5_particle.nsize_y = lsize[1]; + h5_particle.nsize_z = lsize[2]; + + Eigen::Vector3d velocity; + velocity << 1.5, 2.5, 3.5; + h5_particle.velocity_x = velocity[0]; + h5_particle.velocity_y = velocity[1]; + h5_particle.velocity_z = velocity[2]; + + Eigen::Matrix stress; + stress << 11.5, -12.5, 13.5, 14.5, -15.5, 16.5; + h5_particle.stress_xx = stress[0]; + h5_particle.stress_yy = stress[1]; + h5_particle.stress_zz = stress[2]; + h5_particle.tau_xy = stress[3]; + h5_particle.tau_yz = stress[4]; + h5_particle.tau_xz = stress[5]; + + Eigen::Matrix strain; + strain << 0.115, -0.125, 0.135, 0.145, -0.155, 0.165; + h5_particle.strain_xx = strain[0]; + h5_particle.strain_yy = strain[1]; + h5_particle.strain_zz = strain[2]; + h5_particle.gamma_xy = strain[3]; + h5_particle.gamma_yz = strain[4]; + h5_particle.gamma_xz = strain[5]; + + h5_particle.epsilon_v = strain.head(Dim).sum(); + + h5_particle.status = true; + + h5_particle.cell_id = 1; + + h5_particle.volume = 2.; + + h5_particle.material_id = 1; + + h5_particle.liquid_mass = 100.1; + + Eigen::Vector3d liquid_velocity; + liquid_velocity << 5.5, 3.12, 2.1; + h5_particle.liquid_velocity_x = liquid_velocity[0]; + h5_particle.liquid_velocity_y = liquid_velocity[1]; + h5_particle.liquid_velocity_z = liquid_velocity[2]; + + h5_particle.porosity = 0.33; + + h5_particle.liquid_saturation = 1.; + + h5_particle.liquid_material_id = 2; + + // Reinitialise particle from POD data + REQUIRE(particle->initialise_particle(h5_particle) == true); + + // Check particle id + REQUIRE(particle->id() == h5_particle.id); + // Check particle mass + REQUIRE(particle->mass() == h5_particle.mass); + // Check particle volume + REQUIRE(particle->volume() == h5_particle.volume); + // Check particle mass density + REQUIRE(particle->mass_density() == h5_particle.mass / h5_particle.volume); + // Check particle status + REQUIRE(particle->status() == h5_particle.status); + + // Check for coordinates + auto coordinates = particle->coordinates(); + REQUIRE(coordinates.size() == Dim); + for (unsigned i = 0; i < coordinates.size(); ++i) + REQUIRE(coordinates(i) == Approx(coords(i)).epsilon(Tolerance)); + REQUIRE(coordinates.size() == Dim); + + // Check for displacement + auto pdisplacement = particle->displacement(); + REQUIRE(pdisplacement.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pdisplacement(i) == Approx(displacement(i)).epsilon(Tolerance)); + + // Check for size + auto size = particle->natural_size(); + REQUIRE(size.size() == Dim); + for (unsigned i = 0; i < size.size(); ++i) + REQUIRE(size(i) == Approx(lsize(i)).epsilon(Tolerance)); + + // Check velocity + auto pvelocity = particle->velocity(); + REQUIRE(pvelocity.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pvelocity(i) == Approx(velocity(i)).epsilon(Tolerance)); + + // Check stress + auto pstress = particle->stress(); + REQUIRE(pstress.size() == stress.size()); + for (unsigned i = 0; i < stress.size(); ++i) + REQUIRE(pstress(i) == Approx(stress(i)).epsilon(Tolerance)); + + // Check strain + auto pstrain = particle->strain(); + REQUIRE(pstrain.size() == strain.size()); + for (unsigned i = 0; i < strain.size(); ++i) + REQUIRE(pstrain(i) == Approx(strain(i)).epsilon(Tolerance)); + + // Check particle volumetric strain centroid + REQUIRE(particle->volumetric_strain_centroid() == h5_particle.epsilon_v); + + // Check cell id + REQUIRE(particle->cell_id() == h5_particle.cell_id); + + // Check material id + REQUIRE(particle->material_id() == h5_particle.material_id); + + // Check liquid mass + REQUIRE(particle->liquid_mass() == h5_particle.liquid_mass); + + // Check liquid velocity + auto pliquid_velocity = particle->liquid_velocity(); + REQUIRE(pliquid_velocity.size() == Dim); + for (unsigned i = 0; i < Dim; ++i) + REQUIRE(pliquid_velocity(i) == + Approx(liquid_velocity(i)).epsilon(Tolerance)); + + // Check porosity + REQUIRE(particle->porosity() == h5_particle.porosity); + + // Check liquid material id + REQUIRE(particle->material_id(mpm::ParticlePhase::Liquid) == + h5_particle.liquid_material_id); + + // Write Particle POD data + auto pod_test = + std::static_pointer_cast(particle->pod()); + + REQUIRE(h5_particle.id == pod_test->id); + REQUIRE(h5_particle.mass == pod_test->mass); + + REQUIRE(h5_particle.coord_x == + Approx(pod_test->coord_x).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_y == + Approx(pod_test->coord_y).epsilon(Tolerance)); + REQUIRE(h5_particle.coord_z == + Approx(pod_test->coord_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.displacement_x == + Approx(pod_test->displacement_x).epsilon(Tolerance)); + REQUIRE(h5_particle.displacement_y == + Approx(pod_test->displacement_y).epsilon(Tolerance)); + REQUIRE(h5_particle.displacement_z == + Approx(pod_test->displacement_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.nsize_x == pod_test->nsize_x); + REQUIRE(h5_particle.nsize_y == pod_test->nsize_y); + REQUIRE(h5_particle.nsize_z == pod_test->nsize_z); + + REQUIRE(h5_particle.velocity_x == + Approx(pod_test->velocity_x).epsilon(Tolerance)); + REQUIRE(h5_particle.velocity_y == + Approx(pod_test->velocity_y).epsilon(Tolerance)); + REQUIRE(h5_particle.velocity_z == + Approx(pod_test->velocity_z).epsilon(Tolerance)); + + REQUIRE(h5_particle.stress_xx == + Approx(pod_test->stress_xx).epsilon(Tolerance)); + REQUIRE(h5_particle.stress_yy == + Approx(pod_test->stress_yy).epsilon(Tolerance)); + REQUIRE(h5_particle.stress_zz == + Approx(pod_test->stress_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xy == Approx(pod_test->tau_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_yz == Approx(pod_test->tau_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.tau_xz == Approx(pod_test->tau_xz).epsilon(Tolerance)); + + REQUIRE(h5_particle.strain_xx == + Approx(pod_test->strain_xx).epsilon(Tolerance)); + REQUIRE(h5_particle.strain_yy == + Approx(pod_test->strain_yy).epsilon(Tolerance)); + REQUIRE(h5_particle.strain_zz == + Approx(pod_test->strain_zz).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_xy == + Approx(pod_test->gamma_xy).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_yz == + Approx(pod_test->gamma_yz).epsilon(Tolerance)); + REQUIRE(h5_particle.gamma_xz == + Approx(pod_test->gamma_xz).epsilon(Tolerance)); + + REQUIRE(h5_particle.epsilon_v == + Approx(pod_test->epsilon_v).epsilon(Tolerance)); + REQUIRE(h5_particle.status == pod_test->status); + REQUIRE(h5_particle.cell_id == pod_test->cell_id); + REQUIRE(h5_particle.material_id == pod_test->material_id); + + REQUIRE(h5_particle.liquid_mass == + Approx(pod_test->liquid_mass).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_x == + Approx(pod_test->liquid_velocity_x).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_y == + Approx(pod_test->liquid_velocity_y).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_velocity_z == + Approx(pod_test->liquid_velocity_z).epsilon(Tolerance)); + REQUIRE(h5_particle.porosity == + Approx(pod_test->porosity).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_saturation == + Approx(pod_test->liquid_saturation).epsilon(Tolerance)); + REQUIRE(h5_particle.liquid_material_id == + Approx(pod_test->liquid_material_id).epsilon(Tolerance)); + } + + // Check particle's material id maping to nodes + SECTION("Check particle's material id maping to nodes") { + // Add particle + mpm::Index id1 = 0; + coords << 1.5, 1.5, 1.5; + auto particle1 = std::make_shared>(id1, coords); + + // Add particle + mpm::Index id2 = 1; + coords << 0.5, 0.5, 0.5; + auto particle2 = std::make_shared>(id2, coords); + + // Element + std::shared_ptr> element = + std::make_shared>(); + + // Create cell + auto cell = std::make_shared>(10, Nnodes, element); + // Create vector of nodes and add them to cell + coords << 0, 0, 0; + std::shared_ptr> node0 = + std::make_shared>(0, coords); + + coords << 2, 0, 0; + std::shared_ptr> node1 = + std::make_shared>(1, coords); + + coords << 2, 2, 0; + std::shared_ptr> node2 = + std::make_shared>(2, coords); + + coords << 0, 2, 0; + std::shared_ptr> node3 = + std::make_shared>(3, coords); + + coords << 0, 0, 2; + std::shared_ptr> node4 = + std::make_shared>(4, coords); + + coords << 2, 0, 2; + std::shared_ptr> node5 = + std::make_shared>(5, coords); + + coords << 2, 2, 2; + std::shared_ptr> node6 = + std::make_shared>(6, coords); + + coords << 0, 2, 2; + std::shared_ptr> node7 = + std::make_shared>(7, coords); + std::vector>> nodes = { + node0, node1, node2, node3, node4, node5, node6, node7}; + + for (int j = 0; j < nodes.size(); ++j) cell->add_node(j, nodes[j]); + + // Initialise cell properties and assign cell to particle + cell->initialise(); + particle1->assign_cell(cell); + particle2->assign_cell(cell); + + // Assign material 1 + unsigned mid1 = 0; + // Initialise material 1 + Json jmaterial1; + jmaterial1["density"] = 1000.; + jmaterial1["youngs_modulus"] = 1.0E+7; + jmaterial1["poisson_ratio"] = 0.3; + + auto material1 = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic3D", std::move(mid1), jmaterial1); + + particle1->assign_material(material1); + + // Assign material 2 + unsigned mid2 = 1; + // Initialise material 2 + Json jmaterial2; + jmaterial2["density"] = 2000.; + jmaterial2["youngs_modulus"] = 2.0E+7; + jmaterial2["poisson_ratio"] = 0.25; + + auto material2 = + Factory, unsigned, const Json&>::instance()->create( + "LinearElastic3D", std::move(mid2), jmaterial2); + + particle2->assign_material(material2); + + // Append particle's material id to nodes in cell + particle1->append_material_id_to_nodes(); + particle2->append_material_id_to_nodes(); + + // check if the correct amount of material ids were added to node and if + // their indexes are correct + std::vector material_ids = {0, 1}; + for (const auto& node : nodes) { + REQUIRE(node->material_ids().size() == 2); + auto mat_ids = node->material_ids(); + unsigned i = 0; + for (auto mitr = mat_ids.begin(); mitr != mat_ids.end(); ++mitr, ++i) + REQUIRE(*mitr == material_ids.at(i)); + } + } +} diff --git a/tests/particle_vector_test.cc b/tests/particles/particle_vector_test.cc similarity index 100% rename from tests/particle_vector_test.cc rename to tests/particles/particle_vector_test.cc diff --git a/tests/solvers/mpm_explicit_twophase_usf_test.cc b/tests/solvers/mpm_explicit_twophase_usf_test.cc new file mode 100644 index 000000000..ec05024ff --- /dev/null +++ b/tests/solvers/mpm_explicit_twophase_usf_test.cc @@ -0,0 +1,213 @@ +#include "catch.hpp" + +//! Alias for JSON +#include "json.hpp" +using Json = nlohmann::json; + +#include "mpm_explicit_twophase.h" +#include "write_mesh_particles.h" + +// Check MPM Explicit +TEST_CASE("MPM 2D Explicit TwoPhase implementation is checked", + "[MPM][2D][Explicit][USF][2Phase]") { + // Dimension + const unsigned Dim = 2; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usf"; + const std::string analysis = "MPMExplicitTwoPhase2D"; + const std::string mpm_scheme = "usf"; + bool resume = false; + REQUIRE(mpm_test::write_json_twophase(2, resume, analysis, mpm_scheme, + fname) == true); + + // Write JSON Entity Sets file + REQUIRE(mpm_test::write_entity_set() == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_2d() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_2d() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-usf-2d.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + // Initialise particles + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Initialise external loading + REQUIRE_NOTHROW(mpm->initialise_loads()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == false); + } + + SECTION("Check resume") { + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usf"; + const std::string analysis = "MPMExplicitTwoPhase2D"; + const std::string mpm_scheme = "usf"; + bool resume = true; + REQUIRE(mpm_test::write_json_twophase(2, resume, analysis, mpm_scheme, + fname) == true); + + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + { + // Solve + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm_resume = + std::make_unique>(std::move(io)); + REQUIRE(mpm_resume->solve() == true); + } + } + + SECTION("Check pressure smoothing") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Pressure smoothing + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Solid)); + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Liquid)); + } +} + +// Check MPM Explicit +TEST_CASE("MPM 3D Explicit TwoPhase implementation is checked", + "[MPM][3D][Explicit][USF][2Phase]") { + // Dimension + const unsigned Dim = 3; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usf"; + const std::string analysis = "MPMExplicitTwoPhase3D"; + const std::string mpm_scheme = "usf"; + const bool resume = false; + REQUIRE(mpm_test::write_json_twophase(3, resume, analysis, mpm_scheme, + fname) == true); + + // Write JSON Entity Sets file + REQUIRE(mpm_test::write_entity_set() == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_3d() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_3d() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-usf-3d.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + // Initialise particles + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == false); + } + + SECTION("Check resume") { + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usf"; + const std::string analysis = "MPMExplicitTwoPhase3D"; + const std::string mpm_scheme = "usf"; + bool resume = true; + REQUIRE(mpm_test::write_json_twophase(3, resume, analysis, mpm_scheme, + fname) == true); + + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + { + // Solve + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm_resume = + std::make_unique>(std::move(io)); + REQUIRE(mpm_resume->solve() == true); + } + } + + SECTION("Check pressure smoothing") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Pressure smoothing + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Solid)); + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Liquid)); + } +} diff --git a/tests/solvers/mpm_explicit_twophase_usf_unitcell_test.cc b/tests/solvers/mpm_explicit_twophase_usf_unitcell_test.cc new file mode 100644 index 000000000..57c3252b8 --- /dev/null +++ b/tests/solvers/mpm_explicit_twophase_usf_unitcell_test.cc @@ -0,0 +1,119 @@ +#include "catch.hpp" + +//! Alias for JSON +#include "json.hpp" +using Json = nlohmann::json; + +#include "mpm_explicit_twophase.h" +#include "write_mesh_particles_unitcell.h" + +// Check MPM Explicit USF +TEST_CASE("MPM 2D Explicit TwoPhase USF implementation is checked in unitcells", + "[MPM][2D][USF][Explicit][2Phase][unitcell]") { + // Dimension + const unsigned Dim = 2; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usf"; + const std::string analysis = "MPMExplicitTwoPhase2D"; + const std::string mpm_scheme = "usf"; + REQUIRE(mpm_test::write_json_unitcell_twophase(2, analysis, mpm_scheme, + fname) == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_2d_unitcell() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_2d_unitcell() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-usf-2d-unitcell.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + + // Initialise mesh and particles + REQUIRE_NOTHROW(mpm->initialise_mesh()); + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Initialise external loading + REQUIRE_NOTHROW(mpm->initialise_loads()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + } +} + +// Check MPM Explicit +TEST_CASE("MPM 3D Explicit TwoPhase USF implementation is checked in unitcells", + "[MPM][3D][Explicit][USF][2Phase][unitcell]") { + // Dimension + const unsigned Dim = 3; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usf"; + const std::string analysis = "MPMExplicitTwoPhase3D"; + const std::string mpm_scheme = "usf"; + REQUIRE(mpm_test::write_json_unitcell_twophase(3, analysis, mpm_scheme, + fname) == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_3d_unitcell() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_3d_unitcell() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-usf-3d-unitcell.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + + // Initialise mesh and particles + REQUIRE_NOTHROW(mpm->initialise_mesh()); + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + } +} diff --git a/tests/solvers/mpm_explicit_twophase_usl_test.cc b/tests/solvers/mpm_explicit_twophase_usl_test.cc new file mode 100644 index 000000000..e74bd5b3c --- /dev/null +++ b/tests/solvers/mpm_explicit_twophase_usl_test.cc @@ -0,0 +1,213 @@ +#include "catch.hpp" + +//! Alias for JSON +#include "json.hpp" +using Json = nlohmann::json; + +#include "mpm_explicit_twophase.h" +#include "write_mesh_particles.h" + +// Check MPM Explicit +TEST_CASE("MPM 2D Explicit USL TwoPhase implementation is checked", + "[MPM][2D][Explicit][USL][2Phase]") { + // Dimension + const unsigned Dim = 2; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usl"; + const std::string analysis = "MPMExplicitTwoPhase2D"; + const std::string mpm_scheme = "usl"; + bool resume = false; + REQUIRE(mpm_test::write_json_twophase(2, resume, analysis, mpm_scheme, + fname) == true); + + // Write JSON Entity Sets file + REQUIRE(mpm_test::write_entity_set() == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_2d() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_2d() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-usl-2d.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + // Initialise particles + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Initialise external loading + REQUIRE_NOTHROW(mpm->initialise_loads()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == false); + } + + SECTION("Check resume") { + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usl"; + const std::string analysis = "MPMExplicitTwoPhase2D"; + const std::string mpm_scheme = "usl"; + bool resume = true; + REQUIRE(mpm_test::write_json_twophase(2, resume, analysis, mpm_scheme, + fname) == true); + + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + { + // Solve + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm_resume = + std::make_unique>(std::move(io)); + REQUIRE(mpm_resume->solve() == true); + } + } + + SECTION("Check pressure smoothing") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Pressure smoothing + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Solid)); + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Liquid)); + } +} + +// Check MPM Explicit +TEST_CASE("MPM 3D Explicit USL TwoPhase implementation is checked", + "[MPM][3D][Explicit][USL][2Phase]") { + // Dimension + const unsigned Dim = 3; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usl"; + const std::string analysis = "MPMExplicitTwoPhase3D"; + const std::string mpm_scheme = "usl"; + const bool resume = false; + REQUIRE(mpm_test::write_json_twophase(3, resume, analysis, mpm_scheme, + fname) == true); + + // Write JSON Entity Sets file + REQUIRE(mpm_test::write_entity_set() == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_3d() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_3d() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-usl-3d.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + // Initialise particles + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == false); + } + + SECTION("Check resume") { + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usl"; + const std::string analysis = "MPMExplicitTwoPhase3D"; + const std::string mpm_scheme = "usl"; + bool resume = true; + REQUIRE(mpm_test::write_json_twophase(3, resume, analysis, mpm_scheme, + fname) == true); + + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + { + // Solve + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm_resume = + std::make_unique>(std::move(io)); + REQUIRE(mpm_resume->solve() == true); + } + } + + SECTION("Check pressure smoothing") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Pressure smoothing + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Solid)); + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Liquid)); + } +} diff --git a/tests/solvers/mpm_explicit_twophase_usl_unitcell_test.cc b/tests/solvers/mpm_explicit_twophase_usl_unitcell_test.cc new file mode 100644 index 000000000..7a8671399 --- /dev/null +++ b/tests/solvers/mpm_explicit_twophase_usl_unitcell_test.cc @@ -0,0 +1,119 @@ +#include "catch.hpp" + +//! Alias for JSON +#include "json.hpp" +using Json = nlohmann::json; + +#include "mpm_explicit_twophase.h" +#include "write_mesh_particles_unitcell.h" + +// Check MPM Explicit USL +TEST_CASE("MPM 2D Explicit TwoPhase USL implementation is checked in unitcells", + "[MPM][2D][USL][Explicit][2Phase][unitcell]") { + // Dimension + const unsigned Dim = 2; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usl"; + const std::string analysis = "MPMExplicitTwoPhase2D"; + const std::string mpm_scheme = "usl"; + REQUIRE(mpm_test::write_json_unitcell_twophase(2, analysis, mpm_scheme, + fname) == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_2d_unitcell() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_2d_unitcell() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-usl-2d-unitcell.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + + // Initialise mesh and particles + REQUIRE_NOTHROW(mpm->initialise_mesh()); + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Initialise external loading + REQUIRE_NOTHROW(mpm->initialise_loads()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + } +} + +// Check MPM Explicit +TEST_CASE("MPM 3D Explicit TwoPhase USL implementation is checked in unitcells", + "[MPM][3D][Explicit][USL][2Phase][unitcell]") { + // Dimension + const unsigned Dim = 3; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase-usl"; + const std::string analysis = "MPMExplicitTwoPhase3D"; + const std::string mpm_scheme = "usl"; + REQUIRE(mpm_test::write_json_unitcell_twophase(3, analysis, mpm_scheme, + fname) == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_3d_unitcell() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_3d_unitcell() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-usl-3d-unitcell.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + + // Initialise mesh and particles + REQUIRE_NOTHROW(mpm->initialise_mesh()); + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + } +} diff --git a/tests/solvers/mpm_semi_implicit_navierstokes_test.cc b/tests/solvers/mpm_semi_implicit_navierstokes_test.cc new file mode 100644 index 000000000..aada5b9f8 --- /dev/null +++ b/tests/solvers/mpm_semi_implicit_navierstokes_test.cc @@ -0,0 +1,232 @@ +#include "catch.hpp" + +//! Alias for JSON +#include "json.hpp" +using Json = nlohmann::json; + +#include "mpm_semi_implicit_navierstokes.h" +#include "write_mesh_particles.h" + +// Check MPM Semi-implicit Navier Stokes +TEST_CASE("MPM 2D Semi-implicit Navier Stokes implementation is checked", + "[MPM][2D][Semi-implicit][2Phase]") { + // Dimension + const unsigned Dim = 2; + + // Write JSON file + const std::string fname = "mpm-semi-implicit-ns"; + const std::string analysis = "MPMSemiImplicitNavierStokes2D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "density"; + const std::string lin_solver_type = "IterativeEigen"; + bool resume = false; + REQUIRE(mpm_test::write_json_navierstokes(2, resume, analysis, mpm_scheme, + fname, fsd_type, + lin_solver_type) == true); + + // Write JSON Entity Sets file + REQUIRE(mpm_test::write_entity_set() == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_2d() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_2d() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-semi-implicit-ns-2d.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + // Initialise particles + REQUIRE_NOTHROW(mpm->initialise_particles()); + // Initialise external loading + REQUIRE_NOTHROW(mpm->initialise_loads()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == false); + } + + SECTION("Check resume") { + // Write JSON file + const std::string fname = "mpm-semi-implicit-ns"; + const std::string analysis = "MPMSemiImplicitNavierStokes2D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "density"; + const std::string lin_solver_type = "IterativeEigen"; + bool resume = true; + REQUIRE(mpm_test::write_json_navierstokes(2, resume, analysis, mpm_scheme, + fname, fsd_type, + lin_solver_type) == true); + + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + { + // Solve + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm_resume = std::make_unique>( + std::move(io)); + REQUIRE(mpm_resume->solve() == true); + } + } + + SECTION("Check pressure smoothing") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Pressure smoothing + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Solid)); + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Liquid)); + } +} + +// Check MPM Semi Implicit +TEST_CASE("MPM 3D Semi-implicit Navier Stokes implementation is checked", + "[MPM][3D][Semi-implicit][1Phase]") { + // Dimension + const unsigned Dim = 3; + + // Write JSON file + const std::string fname = "mpm-semi-implicit-ns"; + const std::string analysis = "MPMSemiImplicitNavierStokes3D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "density"; + const std::string lin_solver_type = "IterativeEigen"; + const bool resume = false; + REQUIRE(mpm_test::write_json_navierstokes(3, resume, analysis, mpm_scheme, + fname, fsd_type, + lin_solver_type) == true); + + // Write JSON Entity Sets file + REQUIRE(mpm_test::write_entity_set() == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_3d() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_3d() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-semi-implicit-ns-3d.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + // Initialise particles + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == false); + } + + SECTION("Check resume") { + // Write JSON file + const std::string fname = "mpm-semi-implicit-ns"; + const std::string analysis = "MPMSemiImplicitNavierStokes3D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "density"; + const std::string lin_solver_type = "IterativeEigen"; + bool resume = true; + REQUIRE(mpm_test::write_json_navierstokes(3, resume, analysis, mpm_scheme, + fname, fsd_type, + lin_solver_type) == true); + + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + { + // Solve + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm_resume = std::make_unique>( + std::move(io)); + REQUIRE(mpm_resume->solve() == true); + } + } + + SECTION("Check pressure smoothing") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Pressure smoothing + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Solid)); + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Liquid)); + } +} diff --git a/tests/solvers/mpm_semi_implicit_navierstokes_unitcell_test.cc b/tests/solvers/mpm_semi_implicit_navierstokes_unitcell_test.cc new file mode 100644 index 000000000..981cfb129 --- /dev/null +++ b/tests/solvers/mpm_semi_implicit_navierstokes_unitcell_test.cc @@ -0,0 +1,131 @@ +#include "catch.hpp" + +//! Alias for JSON +#include "json.hpp" +using Json = nlohmann::json; + +#include "mpm_semi_implicit_navierstokes.h" +#include "write_mesh_particles_unitcell.h" + +// Check MPM Semi-implicit Navier Stokes +TEST_CASE( + "MPM 2D Semi-implicit Navier Stokes implementation is checked in unitcells", + "[MPM][2D][Semi-implicit][1Phase][unitcell]") { + // Dimension + const unsigned Dim = 2; + + // Write JSON file + const std::string fname = "mpm-explicit-ns"; + const std::string analysis = "MPMSemiImplicitNavierStokes2D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "geometry"; + const std::string lin_solver_type = "IterativeEigen"; + REQUIRE(mpm_test::write_json_unitcell_navierstokes(2, analysis, mpm_scheme, + fname, fsd_type, + lin_solver_type) == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_2d_unitcell() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_2d_unitcell() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-ns-2d-unitcell.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + + // Initialise mesh and particles + REQUIRE_NOTHROW(mpm->initialise_mesh()); + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Initialise external loading + REQUIRE_NOTHROW(mpm->initialise_loads()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + } +} + +// Check MPM Semi Implicit +TEST_CASE( + "MPM 3D Semi-implicit Navier Stokes implementation is checked in unitcells", + "[MPM][3D][Semi-implicit][1Phase][unitcell]") { + // Dimension + const unsigned Dim = 3; + + // Write JSON file + const std::string fname = "mpm-explicit-ns"; + const std::string analysis = "MPMSemiImplicitNavierStokes3D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "geometry"; + const std::string lin_solver_type = "IterativeEigen"; + REQUIRE(mpm_test::write_json_unitcell_navierstokes(3, analysis, mpm_scheme, + fname, fsd_type, + lin_solver_type) == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_3d_unitcell() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_3d_unitcell() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-ns-3d-unitcell.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + + // Initialise mesh and particles + REQUIRE_NOTHROW(mpm->initialise_mesh()); + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + } +} diff --git a/tests/solvers/mpm_semi_implicit_twophase_test.cc b/tests/solvers/mpm_semi_implicit_twophase_test.cc new file mode 100644 index 000000000..2ef704a4e --- /dev/null +++ b/tests/solvers/mpm_semi_implicit_twophase_test.cc @@ -0,0 +1,230 @@ +#include "catch.hpp" + +//! Alias for JSON +#include "json.hpp" +using Json = nlohmann::json; + +#include "mpm_semi_implicit_twophase.h" +#include "write_mesh_particles.h" + +// Check MPM Semi-implicit TwoPhase +TEST_CASE("MPM 2D Semi-implicit TwoPhase implementation is checked", + "[MPM][2D][Semi-implicit][2Phase]") { + // Dimension + const unsigned Dim = 2; + + // Write JSON file + const std::string fname = "mpm-semi-implicit-twophase"; + const std::string analysis = "MPMSemiImplicitTwoPhase2D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "density"; + const std::string lin_solver_type = "IterativeEigen"; + bool resume = false; + REQUIRE(mpm_test::write_json_twophase(2, resume, analysis, mpm_scheme, fname, + fsd_type, lin_solver_type) == true); + + // Write JSON Entity Sets file + REQUIRE(mpm_test::write_entity_set() == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_2d() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_2d() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-semi-implicit-twophase-2d.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + // Initialise particles + REQUIRE_NOTHROW(mpm->initialise_particles()); + // Initialise external loading + REQUIRE_NOTHROW(mpm->initialise_loads()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == false); + } + + SECTION("Check resume") { + // Write JSON file + const std::string fname = "mpm-semi-implicit-twophase"; + const std::string analysis = "MPMSemiImplicitTwoPhase2D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "density"; + const std::string lin_solver_type = "IterativeEigen"; + bool resume = true; + REQUIRE(mpm_test::write_json_twophase(2, resume, analysis, mpm_scheme, + fname, fsd_type, + lin_solver_type) == true); + + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + { + // Solve + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm_resume = + std::make_unique>(std::move(io)); + REQUIRE(mpm_resume->solve() == true); + } + } + + SECTION("Check pressure smoothing") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Pressure smoothing + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Solid)); + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Liquid)); + } +} + +// Check MPM Semi Implicit +TEST_CASE("MPM 3D Semi-implicit TwoPhase implementation is checked", + "[MPM][3D][Semi-implicit][2Phase]") { + // Dimension + const unsigned Dim = 3; + + // Write JSON file + const std::string fname = "mpm-semi-implicit-twophase"; + const std::string analysis = "MPMSemiImplicitTwoPhase3D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "density"; + const std::string lin_solver_type = "IterativeEigen"; + const bool resume = false; + REQUIRE(mpm_test::write_json_twophase(3, resume, analysis, mpm_scheme, fname, + fsd_type, lin_solver_type) == true); + + // Write JSON Entity Sets file + REQUIRE(mpm_test::write_entity_set() == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_3d() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_3d() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-semi-implicit-twophase-3d.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + // Initialise particles + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == false); + } + + SECTION("Check resume") { + // Write JSON file + const std::string fname = "mpm-semi-implicit-twophase"; + const std::string analysis = "MPMSemiImplicitTwoPhase3D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "density"; + const std::string lin_solver_type = "IterativeEigen"; + bool resume = true; + REQUIRE(mpm_test::write_json_twophase(3, resume, analysis, mpm_scheme, + fname, fsd_type, + lin_solver_type) == true); + + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + // Initialise mesh + REQUIRE_NOTHROW(mpm->initialise_mesh()); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + { + // Solve + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm_resume = + std::make_unique>(std::move(io)); + REQUIRE(mpm_resume->solve() == true); + } + } + + SECTION("Check pressure smoothing") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run Semi Implicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Pressure smoothing + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Solid)); + REQUIRE_NOTHROW(mpm->pressure_smoothing(mpm::ParticlePhase::Liquid)); + } +} diff --git a/tests/solvers/mpm_semi_implicit_twophase_unitcell_test.cc b/tests/solvers/mpm_semi_implicit_twophase_unitcell_test.cc new file mode 100644 index 000000000..a35b9a856 --- /dev/null +++ b/tests/solvers/mpm_semi_implicit_twophase_unitcell_test.cc @@ -0,0 +1,131 @@ +#include "catch.hpp" + +//! Alias for JSON +#include "json.hpp" +using Json = nlohmann::json; + +#include "mpm_semi_implicit_twophase.h" +#include "write_mesh_particles_unitcell.h" + +// Check MPM Semi-implicit TwoPhase +TEST_CASE( + "MPM 2D Semi-implicit TwoPhase implementation is checked in unitcells", + "[MPM][2D][Semi-implicit][2Phase][unitcell]") { + // Dimension + const unsigned Dim = 2; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase"; + const std::string analysis = "MPMSemiImplicitTwoPhase2D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "geometry"; + const std::string lin_solver_type = "IterativeEigen"; + REQUIRE(mpm_test::write_json_unitcell_twophase(2, analysis, mpm_scheme, fname, + fsd_type, + lin_solver_type) == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_2d_unitcell() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_2d_unitcell() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-2d-unitcell.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + + // Initialise mesh and particles + REQUIRE_NOTHROW(mpm->initialise_mesh()); + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Initialise external loading + REQUIRE_NOTHROW(mpm->initialise_loads()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + } +} + +// Check MPM Semi Implicit +TEST_CASE( + "MPM 3D Semi-implicit TwoPhase implementation is checked in unitcells", + "[MPM][3D][Semi-implicit][2Phase][unitcell]") { + // Dimension + const unsigned Dim = 3; + + // Write JSON file + const std::string fname = "mpm-explicit-twophase"; + const std::string analysis = "MPMSemiImplicitTwoPhase3D"; + const std::string mpm_scheme = "usf"; + const std::string fsd_type = "geometry"; + const std::string lin_solver_type = "IterativeEigen"; + REQUIRE(mpm_test::write_json_unitcell_twophase(3, analysis, mpm_scheme, fname, + fsd_type, + lin_solver_type) == true); + + // Write Mesh + REQUIRE(mpm_test::write_mesh_3d_unitcell() == true); + + // Write Particles + REQUIRE(mpm_test::write_particles_3d_unitcell() == true); + + // Assign argc and argv to input arguments of MPM + int argc = 5; + // clang-format off + char* argv[] = {(char*)"./mpm", + (char*)"-f", (char*)"./", + (char*)"-i", (char*)"mpm-explicit-twophase-3d-unitcell.json"}; + // clang-format on + + SECTION("Check initialisation") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = + std::make_unique>(std::move(io)); + + // Initialise materials + REQUIRE_NOTHROW(mpm->initialise_materials()); + + // Initialise mesh and particles + REQUIRE_NOTHROW(mpm->initialise_mesh()); + REQUIRE_NOTHROW(mpm->initialise_particles()); + + // Renitialise materials + REQUIRE_THROWS(mpm->initialise_materials()); + } + + SECTION("Check solver") { + // Create an IO object + auto io = std::make_unique(argc, argv); + // Run explicit MPM + auto mpm = + std::make_unique>(std::move(io)); + // Solve + REQUIRE(mpm->solve() == true); + } +} diff --git a/tests/test_main.cc b/tests/test_main.cc index c18bce4ba..adbb78564 100644 --- a/tests/test_main.cc +++ b/tests/test_main.cc @@ -8,6 +8,9 @@ #ifdef USE_MPI #include "mpi.h" #endif +#ifdef USE_PETSC +#include +#endif int main(int argc, char** argv) { try { @@ -22,8 +25,18 @@ int main(int argc, char** argv) { MPI_Init(&argc, &argv); #endif +#ifdef USE_PETSC + // Initialize PETSc + PetscInitialize(&argc, &argv, 0, 0); +#endif + int result = session.run(); +#ifdef USE_PETSC + // Finalize PETSc + PetscFinalize(); +#endif + #ifdef USE_MPI MPI_Finalize(); #endif