diff --git a/.gitignore b/.gitignore index 828560b3a..ee24fee34 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ configure.scan *missing *stamp-h1 benchmarks/ +bin/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 284ae8d23..87f33bb06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,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 @@ -142,8 +143,10 @@ include_directories(BEFORE ${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,7 +160,8 @@ 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 @@ -191,6 +195,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 @@ -205,25 +210,33 @@ 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_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_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/include/cell.h b/include/cell.h index 2380b7d3d..ae52573fa 100644 --- a/include/cell.h +++ b/include/cell.h @@ -217,6 +217,50 @@ 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); + + /**@}*/ + private: //! Approximately check if a point is in a cell //! \param[in] point Coordinates of point @@ -264,6 +308,12 @@ class Cell { //! Normal of face //! first-> face_id, second->vector of the normal std::map face_normals_; + //! Solving status + bool solving_status_{false}; + //! Free surface bool + bool free_surface_{false}; + //! Volume fraction + double volume_fraction_{0.0}; //! Logger std::unique_ptr console_; }; // Cell class @@ -271,4 +321,4 @@ class Cell { #include "cell.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..2bbbc5731 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 // | \ // | \ @@ -844,3 +844,17 @@ template inline unsigned mpm::Cell::previous_mpirank() const { return this->previous_mpirank_; } + +//! 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_); + } + } +} 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 082a0e874..43087ea4b 100644 --- a/include/io/logger.h +++ b/include/io/logger.h @@ -36,6 +36,9 @@ struct Logger { // Create a logger for MPM Explicit static const std::shared_ptr mpm_explicit_logger; + // Create a logger for MPM Explicit Two Phase + static const std::shared_ptr mpm_explicit_two_phase_logger; + // Create a logger for MPM Explicit USF static const std::shared_ptr mpm_explicit_usf_logger; 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/loads_bcs/constraints.h b/include/loads_bcs/constraints.h index 0efdef6f6..01e72c8cf 100644 --- a/include/loads_bcs/constraints.h +++ b/include/loads_bcs/constraints.h @@ -48,6 +48,21 @@ class Constraints { const std::vector>& friction_constraints); + //! Assign nodal velocity 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, + const unsigned phase, const 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..a688ae8de 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, + const unsigned phase, const 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/mesh.h b/include/mesh.h index 4b0bc3b68..d94739767 100644 --- a/include/mesh.h +++ b/include/mesh.h @@ -30,7 +30,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 +38,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" @@ -237,6 +238,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. @@ -248,6 +253,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 @@ -389,20 +400,35 @@ 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); + + //! 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 + //! \retval status Status of reading HDF5 output + bool read_particles_hdf5(const std::string& filename, + const std::string& type_name); //! Read 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 reading HDF5 output - bool read_particles_hdf5(unsigned phase, const std::string& filename); + bool read_particles_hdf5(const std::string& filename); + + //! Read HDF5 particles for twophase particle + //! \param[in] filename Name of HDF5 file to write particles data + //! \retval status Status of reading HDF5 output + bool read_particles_hdf5_twophase(const std::string& filename); //! 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; @@ -461,6 +487,73 @@ 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()); + + //! Get free surface node set + //! \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); + + /**@}*/ + private: // Read particles from file //! \param[in] pset_id Set ID of the particles @@ -533,5 +626,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 b531cb68b..27bc733c8 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) { @@ -1068,6 +1079,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 @@ -1427,20 +1448,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; @@ -1452,20 +1511,32 @@ 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 with type name +template +bool mpm::Mesh::read_particles_hdf5(const std::string& filename, + const std::string& type_name) { + bool status = false; + if (type_name == "particles") + status = this->read_particles_hdf5(filename); + else if (type_name == "twophase_particles") + status = this->read_particles_hdf5_twophase(filename); + return status; +} + //! Write particles to HDF5 template -bool mpm::Mesh::read_particles_hdf5(unsigned phase, - const std::string& filename) { +bool mpm::Mesh::read_particles_hdf5(const std::string& filename) { // Create a new file using default properties. hid_t file_id = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); @@ -1477,15 +1548,15 @@ 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()); // Vector of particles Vector> particles; @@ -1496,11 +1567,74 @@ bool mpm::Mesh::read_particles_hdf5(unsigned phase, unsigned i = 0; for (auto pitr = particles_.cbegin(); pitr != particles_.cend(); ++pitr) { if (i < nrecords) { - HDF5Particle particle = dst_buf[i]; + PODParticle particle = dst_buf[i]; // Get particle's material from list of materials - auto material = materials_.at(particle.material_id); + std::vector>> materials; + materials.emplace_back(materials_.at(particle.material_id)); + // Initialise particle with HDF5 data - (*pitr)->initialise_particle(particle, material); + (*pitr)->initialise_particle(particle, materials); + // Add particle to map + map_particles_.insert(particle.id, *pitr); + particles.add(*pitr); + ++i; + } + } + // close the file + H5Fclose(file_id); + + // Overwrite particles container + this->particles_ = particles; + + // Remove associated cell for the particle + for (auto citr = this->cells_.cbegin(); citr != this->cells_.cend(); ++citr) + (*citr)->clear_particle_ids(); + + return true; +} + +//! Write particles to HDF5 for twophase particles +template +bool mpm::Mesh::read_particles_hdf5_twophase( + const std::string& filename) { + + // 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()); + + // Vector of particles + Vector> particles; + + // Clear map of particles + map_particles_.clear(); + + unsigned i = 0; + for (auto pitr = particles_.cbegin(); pitr != particles_.cend(); ++pitr) { + if (i < nrecords) { + PODParticleTwoPhase particle = dst_buf[i]; + // Get particle's material from list of materials + std::vector>> materials; + materials.emplace_back(materials_.at(particle.material_id)); + materials.emplace_back(materials_.at(particle.liquid_material_id)); + + // Initialise particle with HDF5 data + (*pitr)->initialise_particle(particle, materials); // Add particle to map map_particles_.insert(particle.id, *pitr); particles.add(*pitr); @@ -1522,14 +1656,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; } @@ -1927,7 +2063,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; @@ -1961,4 +2098,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..4b1b4b99d --- /dev/null +++ b/include/mesh_multiphase.tcc @@ -0,0 +1,477 @@ +//! 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; + 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); + 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()]; + + 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); + + 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()); + + 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); + + 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 + + // 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; +} \ No newline at end of file diff --git a/include/node.h b/include/node.h index 31a6b9a03..3ecd9af35 100644 --- a/include/node.h +++ b/include/node.h @@ -134,6 +134,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( + const unsigned phase, const 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 +279,63 @@ 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 { + free_surface_ = free_surface; + } + + //! 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; + + /**@}*/ + private: //! Mutex SpinMutex node_mutex_; @@ -287,6 +359,8 @@ class Node : public NodeBase { Eigen::Matrix external_force_; //! Internal force Eigen::Matrix internal_force_; + //! Drag force + Eigen::Matrix drag_force_coefficient_; //! Pressure Eigen::Matrix pressure_; //! Displacement @@ -309,6 +383,10 @@ class Node : public NodeBase { //! Frictional constraints bool friction_{false}; std::tuple friction_constraint_; + //! Pressure constraint + std::map pressure_constraints_; + //! Mathematical function for pressure + std::map> pressure_function_; //! Concentrated force Eigen::Matrix concentrated_force_; //! Mathematical function for force @@ -319,9 +397,14 @@ class Node : public NodeBase { std::unique_ptr console_; //! MPI ranks std::set mpi_ranks_; + //! Interpolated density + Eigen::Matrix density_; + //! Free surface + bool free_surface_{false}; }; // 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..7040e0b7f 100644 --- a/include/node.tcc +++ b/include/node.tcc @@ -32,6 +32,7 @@ void mpm::Node::initialise() noexcept { velocity_.setZero(); momentum_.setZero(); acceleration_.setZero(); + free_surface_ = false; status_ = false; material_ids_.clear(); } @@ -172,6 +173,48 @@ void mpm::Node::update_mass_pressure( } } +//! Assign pressure constraint +template +bool mpm::Node::assign_pressure_constraint( + const unsigned phase, const double pressure, + const std::shared_ptr& function) { + bool status = true; + try { + // Constrain directions can take values between 0 and Tnphases + assert(phase < Tnphases * 2); + + 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))); + + } 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 +597,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 +606,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 +618,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 +629,8 @@ void mpm::Nodeassign_property( "displacements", prop_id_, *mitr, material_displacement / material_mass(0, 0), Tdim); @@ -630,4 +673,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 +276,57 @@ 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 + 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; + + /**@}*/ + }; // NodeBase class } // namespace mpm diff --git a/include/node_multiphase.tcc b/include/node_multiphase.tcc new file mode 100644 index 000000000..4b7725462 --- /dev/null +++ b/include/node_multiphase.tcc @@ -0,0 +1,157 @@ +//! 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; +} \ No newline at end of file diff --git a/include/particles/particle.h b/include/particles/particle.h index a362d7c76..f998bb112 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; - - //! 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; + PODParticle& particle, + const std::vector>>& materials) 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_; } @@ -234,6 +228,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 +268,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 +307,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 +364,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 +373,9 @@ 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: + protected: //! particle id using ParticleBase::id_; //! coordinates @@ -384,6 +426,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 8335a8379..351a1a63d 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; @@ -102,16 +102,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(); @@ -132,12 +136,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(); @@ -161,63 +165,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; } @@ -240,6 +244,7 @@ void mpm::Particle::initialise() { stress_.setZero(); traction_.setZero(); velocity_.setZero(); + normal_.setZero(); volume_ = std::numeric_limits::max(); volumetric_strain_centroid_ = 0.; @@ -249,6 +254,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(); }; } @@ -284,6 +290,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( @@ -499,6 +513,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 { @@ -842,6 +865,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; @@ -894,6 +921,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( @@ -1035,9 +1083,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..2baee011a 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, + Solid = 0, + Liquid = 1, + Gas = 2, + Mixture = 0 +}; //! 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; @@ -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,107 @@ class ParticleBase { const std::vector& buffer, std::vector>>& materials) = 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; + }; + //---------------------------------------------------------------------------- + protected: //! particleBase id Index id_{std::numeric_limits::max()}; diff --git a/include/particles/particle_twophase.h b/include/particles/particle_twophase.h new file mode 100644 index 000000000..b850c6d4b --- /dev/null +++ b/include/particles/particle_twophase.h @@ -0,0 +1,287 @@ +#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 (water) +//! 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; + + 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_; + //! 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..9cd5647a4 --- /dev/null +++ b/include/particles/particle_twophase.tcc @@ -0,0 +1,1242 @@ +//! 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; + this->assign_permeability(); + + // 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!"); + } + } + 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 liquid phase strain rate at cell centre + 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_.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_; + + 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_; + force[1] = dn_dx_(i, 1) * pressure * this->porosity_; + + 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_; + force[1] = dn_dx_(i, 1) * pressure * this->porosity_; + force[2] = dn_dx_(i, 2) * pressure * this->porosity_; + + 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; + + // 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; + total_stress(1) += 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::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; + total_stress(1) += pressure; + total_stress(2) += 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::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 +} \ 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..7ccf9e16f 100644 --- a/include/solvers/mpm.h +++ b/include/solvers/mpm.h @@ -67,6 +67,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 30b4e06ae..7c4b27eeb 100644 --- a/include/solvers/mpm_base.h +++ b/include/solvers/mpm_base.h @@ -84,6 +84,9 @@ class MPMBase : public MPM { //! Write HDF5 files void write_hdf5(mpm::Index step, mpm::Index max_steps) override; + //! Write HDF5 files for twophase particles + 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; @@ -120,6 +123,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 @@ -151,6 +160,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); + //! Particle entity sets //! \param[in] mesh_prop Mesh properties //! \param[in] check Check duplicates @@ -196,6 +212,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 diff --git a/include/solvers/mpm_base.tcc b/include/solvers/mpm_base.tcc index 9b0674d99..67f81dec6 100644 --- a/include/solvers/mpm_base.tcc +++ b/include/solvers/mpm_base.tcc @@ -236,6 +236,9 @@ void mpm::MPMBase::initialise_mesh() { // Read and assign friction constraints this->nodal_frictional_constraints(mesh_props, mesh_io); + // Read and assign pore pressure constraints + this->nodal_pressure_constraints(mesh_props, mesh_io); + // Initialise cell auto cells_begin = std::chrono::steady_clock::now(); // Shape function name @@ -303,6 +306,10 @@ void mpm::MPMBase::initialise_particles() { if (!gen_status) std::runtime_error( "mpm::base::init_particles() Generate particles failed"); + // Gather particle types + auto particle_type = + json_particle["generator"]["particle_type"].template get(); + particle_types_.insert(particle_type); } auto particles_gen_end = std::chrono::steady_clock::now(); @@ -348,6 +355,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( @@ -359,7 +369,6 @@ void mpm::MPMBase::initialise_particles() { this->particle_entity_sets(mesh_props, check_duplicates); auto particles_sets_end = std::chrono::steady_clock::now(); - // Read and assign particles velocity constraints this->particle_velocity_constraints(mesh_props, particle_io); console_->info("Rank {} Create particle sets: {} ms", mpi_rank, @@ -428,9 +437,6 @@ 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); @@ -445,15 +451,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); + } // Clear all particle ids mesh_->iterate_over_cells( @@ -489,8 +497,21 @@ void mpm::MPMBase::write_hdf5(mpm::Index step, mpm::Index max_steps) { 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(particles_file); +} + +//! Write HDF5 files for twophase particles +template +void mpm::MPMBase::write_hdf5_twophase(mpm::Index step, + mpm::Index max_steps) { + // Write input geometry to vtk file + std::string attribute = "twophase_particles"; + std::string extension = ".h5"; + + auto particles_file = + io_->output_file(attribute, extension, uuid_, step, max_steps).string(); + + mesh_->write_particles_hdf5_twophase(particles_file); } #ifdef USE_VTK @@ -939,6 +960,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, @@ -1082,6 +1155,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(const Json& mesh_props, @@ -1204,6 +1339,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; 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..13c590fde --- /dev/null +++ b/include/solvers/mpm_explicit_twophase.tcc @@ -0,0 +1,366 @@ +//! 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(); + + // Initialise particles + this->initialise_particles(); + + // Initialise loading conditions + this->initialise_loads(); + + // 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)); + + // Check point resume + if (resume) this->checkpoint_resume(); + + // Domain decompose + bool initial_step = (resume == true) ? false : true; + this->mpi_domain_decompose(initial_step); + + 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") { + // TODO: Parallel free-surface computation is not yet implemented + 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/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 82e2b262c..652c2aab4 100644 --- a/src/io/logger.cc +++ b/src/io/logger.cc @@ -28,6 +28,11 @@ const std::shared_ptr mpm::Logger::mpm_base_logger = const std::shared_ptr mpm::Logger::mpm_explicit_logger = spdlog::stdout_color_st("MPMExplicit"); +// 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 Explicit USF const std::shared_ptr mpm::Logger::mpm_explicit_usf_logger = spdlog::stdout_color_st("MPMExplicitUSF"); 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/mpm.cc b/src/mpm.cc index d57b3a9a8..4e71fb1d0 100644 --- a/src/mpm.cc +++ b/src/mpm.cc @@ -4,6 +4,7 @@ #include "io.h" #include "mpm.h" #include "mpm_explicit.h" +#include "mpm_explicit_twophase.h" namespace mpm { // 2D Explicit MPM @@ -14,4 +15,14 @@ static Register, const std::shared_ptr&> static Register, const std::shared_ptr&> mpm_explicit_3d("MPMExplicit3D"); +// 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"); + } // 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..20f0bc997 100644 --- a/src/particle.cc +++ b/src/particle.cc @@ -1,11 +1,19 @@ #include "particle.h" #include "factory.h" #include "particle_base.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}, {"P2D2PHASE", 2}, {"P3D2PHASE", 3}}; +std::map ParticleTypeName = { + {0, "P2D"}, {1, "P3D"}, {2, "P2D2PHASE"}, {3, "P3D2PHASE"}}; +std::map ParticlePODTypeName = { + {"P2D", "particles"}, + {"P3D", "particles"}, + {"P2D2PHASE", "twophase_particles"}, + {"P3D2PHASE", "twophase_particles"}}; } // namespace mpm // Particle2D (2 Dim) @@ -17,3 +25,13 @@ static Register, mpm::Particle<2>, mpm::Index, static Register, mpm::Particle<3>, mpm::Index, const Eigen::Matrix&> particle3d("P3D"); + +// Second phase particle2D (2 Dim) +static Register, mpm::TwoPhaseParticle<2>, mpm::Index, + const Eigen::Matrix&> + particle2d2phase("P2D2PHASE"); + +// Second phase particle3D (3 Dim) +static Register, mpm::TwoPhaseParticle<3>, mpm::Index, + const Eigen::Matrix&> + particle3d2phase("P3D2PHASE"); \ No newline at end of file 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..4457abb60 100644 --- a/tests/include/write_mesh_particles.h +++ b/tests/include/write_mesh_particles.h @@ -8,6 +8,11 @@ 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 twophase +bool write_json_twophase(unsigned dim, bool resume, const std::string& analysis, + const std::string& mpm_scheme, + const std::string& file_name); + // 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..677e05c7d 100644 --- a/tests/include/write_mesh_particles_unitcell.h +++ b/tests/include/write_mesh_particles_unitcell.h @@ -9,6 +9,11 @@ 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 two-phase +bool write_json_unitcell_twophase(unsigned dim, const std::string& analysis, + const std::string& mpm_scheme, + const std::string& file_name); + // 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 b69637511..7d9b8aea6 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/"}, @@ -113,6 +112,137 @@ bool write_json(unsigned dim, bool resume, const std::string& analysis, 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) { + // 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"; + 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"; + 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 + ".txt"}, + {"entity_sets", "entity_sets_0.json"}, + {"io_type", io_type}, + {"check_duplicates", true}, + {"isoparametric", false}, + {"node_type", node_type}, + {"boundary_conditions", + {{"velocity_constraints", {{"file", "velocity-constraints.txt"}}}, + {"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", 2300.}, + {"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}}}, + {"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 diff --git a/tests/io/write_mesh_particles_unitcell.cc b/tests/io/write_mesh_particles_unitcell.cc index f6a17caec..c09acec6b 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 @@ -87,12 +87,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 +111,128 @@ bool write_json_unitcell(unsigned dim, const std::string& analysis, 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) { + // 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"; + std::string material = "LinearElastic2D"; + std::string liquid_material = "Newtonian2D"; + std::vector material_id{{1, 2}}; + std::vector gravity{{0., -9.81}}; + 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"; + 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.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}, + {"dt", 0.0001}, + {"nsteps", 10}, + {"boundary_friction", 0.5}, + {"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 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..bdff4cad1 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,31 @@ 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); + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, set_id, 1, pressure) == true); + + // Add pressure constraint to all nodes in mesh + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, -1, 0, pressure) == true); + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, -1, 1, pressure) == true); + } + // Test assign velocity constraints to nodes SECTION("Check assign velocity constraints to nodes") { // Vector of particle coordinates @@ -1192,6 +1220,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) == true); + } + // 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..3b7a80b74 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,31 @@ 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); + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, set_id, 1, pressure) == true); + + // Add pressure constraint to all nodes in mesh + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, -1, 0, pressure) == true); + REQUIRE(constraints->assign_nodal_pressure_constraint( + mfunction, -1, 1, pressure) == true); + } + SECTION("Check assign friction constraints to nodes") { tsl::robin_map> node_sets; node_sets[0] = std::vector{0, 2}; @@ -1303,6 +1331,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) == true); + } + // 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..f84383299 --- /dev/null +++ b/tests/nodes/node_twophase_test.cc @@ -0,0 +1,2599 @@ +#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); + // 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); + // 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); + // 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..d291fca98 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,34 @@ 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->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()); + } } 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..397cd9980 --- /dev/null +++ b/tests/particles/particle_twophase_test.cc @@ -0,0 +1,3282 @@ +#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(-783333333.3333334923).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 << 588725961.538461566, 590168269.2307692766, + -588533653.8461539745, 196530448.7179487348, + -196241987.1794872284, -196722756.4102564454, + 196049679.4871795177, -589975961.5384616852; + // 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 << 104755.7997557998, 105122.1191221001, + -314120.8791208792, 104976.59897558, + -314267.3992673994, -315364.2813663005, + 104609.2796092796, -315216.7612197804; + nodal_liquid_velocity << 104444.4444444445, 104444.4634444445, + -313333.3333333334, 104445.4634444445, + -313333.3333333334, -313331.3143333333, + 104444.4444444445, -313330.3143333334; + // 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 << 1047557.9975579976, 1051211.1912210013, + -3141208.791208792, 1049745.9897558, + -3142673.9926739936, -3153672.8136630044, + 1046092.7960927964, -3152207.6121978033; + nodal_liquid_acceleration << 1044444.4444444446, 1044434.6344444447, + -3133333.333333334, 1044434.6344444446, + -3133333.333333334, -3133343.1433333335, + 1044444.4444444446, -3133343.1433333335; + // 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(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(-1608333333.3333332539).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 << 402696314.1025640965, 403225160.2564102411, 403826121.7948717475, + -402984775.6410256028, 1209771634.6153848171, 1211670673.0769231319, + -1208665865.3846151829, -1205637019.2307691574, 3630973557.6923074722, + 1208185096.1538460255, -401975160.2564102411, 1210132211.5384614468, + 1208281249.9999997616, 1208329326.9230768681, -402672275.64102566242, + -1208377403.8461537361, 3625276442.3076920509, -1207439903.8461537361, + -3624266826.9230761528, -3629026442.3076920509, -3634435096.153845787, + 3625132211.5384612083, -1209963942.3076925278, -1212055288.4615385532; + // 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 << 460224.35897435899824, 460829.75457875471329, 461516.16633699642261, + -153518.00976800979697, 460867.38461538479896, 461591.42641025653575, + -153481.37973137974041, -153093.76434676436475, 461080.60589743591845, + 460260.98901098914212, -153129.39438339442131, 461009.34582417592173, + 460297.61904761905316, 460320.93406593421241, -153390.36357753362972, + -153444.74969474971294, 460358.56410256412346, -153315.10350427351659, + -153408.11965811965638, -153602.58485958489473, -153825.92401709404658, + 460334.24908424913883, -153638.2148962149513, -153897.1840903541306; + // 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 << 4602243.5897435899824, 4608287.5457875467837, 4615141.6633699638769, + -1535180.0976800979115, 4608653.8461538478732, 4615874.2641025651246, + -1534813.7973137972876, -1530967.643467643531, 4610746.0589743591845, + 4602609.8901098910719, -1531333.9438339441549, 4610013.4582417588681, + 4602976.1904761902988, 4603159.3406593417749, -1534003.6357753362972, + -1534447.4969474971294, 4603525.6410256410018, -1533271.0350427350495, + -1534081.1965811965056, -1536095.8485958487727, -1538399.2401709402911, + 4603342.4908424913883, -1536462.1489621493965, -1539131.840903541306; + // 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(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..f98535f7d --- /dev/null +++ b/tests/solvers/mpm_explicit_twophase_usf_test.cc @@ -0,0 +1,191 @@ +#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)); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + // Solve + REQUIRE(mpm->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)); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + // Solve + REQUIRE(mpm->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..2b8330032 --- /dev/null +++ b/tests/solvers/mpm_explicit_twophase_usl_test.cc @@ -0,0 +1,191 @@ +#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)); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + // Solve + REQUIRE(mpm->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)); + + // Test check point restart + REQUIRE(mpm->checkpoint_resume() == true); + // Solve + REQUIRE(mpm->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); + } +}